On many sites ( particularly CMS style business sites) there are 2, 3 4 or sometimes more boxes usually containing a title, icon and some text. These appear under the header linking to strategic parts of the site.

I have set up my custom theme so that the user can decide which parts of the site to link these boxes to via the theme options; but I can think of at least 2 or three other ways of doing this, and I'm wondering if I've made the right choice.

The alternative options include

  1. Setting it up as a WordPress custom menu ( which would only display the first 4 links in my case ) (though wrt menus, I was really hoping to display these 4 links in div's not ul's so I'm not sure about menus
  2. a custom post type where the user could set up 'tiles' or something like that, working in a similar way.
  3. I also could imagine a special category and using a query but I don't think that would work for me.

Has anyone got any wisdom/experience/pros&cons to share on this? Is there any reason to completely avoid one of the options?

  • Do you mean something like the header on my website? seriouslyfish.com ? If so, I made a simple CPT with really basic options. User can then set the title, link, image etc. from the WP admin interface. Can post up more details if this is what you're looking for.
    – turbonerd
    Jun 23, 2012 at 13:21
  • @dunc Similar but actually more like this - waltontransport.com the difference is that there is a limited number of links, in this case 2, but 4 is probably the most common. The limited number makes me think that a CPT or menu might be a bit confusing for some users. I would gravitate more towards theme options myself, but then I wonder is this the right place for it seeing as we have CPT's and custom menus. Again though the fixed number of 'slots' is the problematic point. Jun 23, 2012 at 13:26
  • I'm referring to the banner at the top of the page, are you? We have a fixed number of slots - 3. I simply show the three (or in your case four/however many) most recent posts.
    – turbonerd
    Jun 23, 2012 at 13:28
  • Right ... I thought you meant the boxes on the left. Interesting to know you used a CPT, do you remember how you came to the idea that it was the way to go, or do you think it's not a big matter which way to go about it? Jun 23, 2012 at 13:31
  • I don't think it's of any import as to how you go about it, as long as you get the desired result for your audience and for your content editors. I wanted a menu in the admin area which contained a rich-text editor, image and hyperlink functionality. The CPT achieved all of this, but I'll admit that a) I didn't really investigate other means because the CPT appears to work seamlessly and b) theme options weren't in WordPress (I believe) when I developed the site.
    – turbonerd
    Jun 23, 2012 at 13:33

1 Answer 1


On my website, I've used Custom Post Types to achieve a slightly more complex version of what you're trying to do.


My requirements were;

  • Rich text on title
  • JavaScript animation to flick between "Featured Items" (not covered here)
  • Ability to edit the featured items easily from the back-end for admin users
  • Ability to hyperlink the featured items to anything at all on our website, be it an article; blog; news post; species profile or glossary entry

I decided that CPTs were the way forward; this is the interface I've developed in the admin area of our somewhat-bespoke WP install.

Admin area

The code for the Custom Post Type itself is fairly straightforward. I created a folder in my plugins directory called sf-featured-items and created a similarly-named php file.

Here is the full code. It looks a lot more complex than it actually is!

   Plugin Name: Seriously Fish: Featured Items
   Plugin URI: http://dunc.seriouslyfish.com
   Description: This plugin allows administrators to control the "Featured.." banner on index.php of Seriously Fish.
   Version: 0.1
   Author: Duncan Wraight
   Author URI: http://dunc.seriouslyfish.com
   License: GPL2

   Copyright 2012  DUNCAN WRAIGHT  (email : [email protected])

   This program is free software; you can redistribute it and/or modify
   it under the terms of the GNU General Public License, version 2, as
   published by the Free Software Foundation.

   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with this program; if not, write to the Free Software
   Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA

if (!class_exists('FeaturedItem')) :

class FeaturedItem {

    var $plugin_url;

    function FeaturedItem()
    $this->plugin_url = trailingslashit(plugins_url('sf-featured-items'));

    function enqueue_scripts() {
        /* start of replacement tinyMCE instance
        wp_register_script('cleditor', $this->plugin_url .'cleditor/jquery.cleditor.min.js', array('jquery'));
    wp_enqueue_script('cleditor'); */

    function enqueue_styles() {
        /* Image upload styles
    wp_enqueue_style('thickbox'); */

    function register_featured_item(){

        $labels = array(
          'name' => _x('Featured Item', 'post type general name'),
          'singular_name' => _x('Featured Item', 'post type singular name'),
          'add_new' => _x('Add New', 'profile'),
          'add_new_item' => __('Add New Featured Item'),
          'edit_item' => __('Edit Featured Item'),
          'new_item' => __('New Featured Item'),
          'view_item' => __('View Featured Item'),
          'search_items' => __('Search Featured Items'),
          'not_found' =>  __('No featured items found'),
          'not_found_in_trash' => __('No featured items found in Trash'),
          'parent_item_colon' => '',
          'menu_name' => 'Featured'

        $args = array(
          'labels' => $labels,
          'public' => true,
          'publicly_queryable' => false,
          'exclude_from_search' => true,    
          'show_ui' => true,
          'show_in_menu' => true,
          'query_var' => true,
          'rewrite' => false,
          'capability_type' => 'post',
          'has_archive' => false,
          'hierarchical' => false,
          'menu_position' => 50,
          'menu_icon' => $this->plugin_url . 'images/star.png',
          'supports' => array('title','editor','thumbnail')

        add_filter('post_updated_messages', 'featured_items_updated_messages');

        function featured_items_updated_messages( $messages ) {
            global $post, $post_ID;

            $messages['species'] = array(
                0 => '', // Unused. Messages start at index 1.
                1 => sprintf( __('Featured item updated. <a href="%s">View</a>.'), esc_url( get_permalink($post_ID) ) ),
                2 => __('Custom field updated.'),
                3 => __('Custom field deleted.'),
                4 => __('Featured item updated.'),
                /* translators: %s: date and time of the revision */
                5 => isset($_GET['revision']) ? sprintf( __('Featured item restored to revision from %s.'), wp_post_revision_title( (int) $_GET['revision'], false ) ) : false,
                6 => sprintf( __('Featured item published. <a href="%s">View</a>.'), esc_url( get_permalink($post_ID) ) ),
                7 => __('Featured item saved.'),
                8 => sprintf( __('Featured item submitted. <a target="_blank" href="%s">Preview</a>.'), esc_url( add_query_arg( 'preview', 'true', get_permalink($post_ID) ) ) ),
                9 => sprintf( __('Featured item scheduled for: <strong>%1$s</strong>. <a target="_blank" href="%2$s">Preview</a>.'),
                // translators: Publish box date format, see http://php.net/date
                date_i18n( __( 'M j, Y @ G:i' ), strtotime( $post->post_date ) ), esc_url( get_permalink($post_ID) ) ),
                10 => sprintf( __('Featured item draft updated. <a target="_blank" href="%s">Preview</a>.'), esc_url( add_query_arg( 'preview', 'true', get_permalink($post_ID) ) ) ),

            return $messages;

        register_post_type('featured-item', $args);

    function meta_link(){
        global $post;

        if ($post->ID) {
            $link_to_post = get_post_meta( $post->ID , 'link_to_post', TRUE );


    <?php wp_nonce_field( plugin_basename( __FILE__ ), 'sf_featured_noncename' ); ?>
    <div class="meta_control">
        <p>Make sure you use the permalink (i.e. written link) rather than the short link (numeric).</p>
        <input type="text" name="link_to_post" class="range" value="<?php if (isset($link_to_post)) { echo esc_attr($link_to_post); } ?>" />

    function register_featured_meta(){
        add_meta_box("link_to_post", "Link to Post", array($this, 'meta_link'), "featured-item", "normal", "core");

    function update_featured_meta(){
        global $post;

        if ($post) {
            if($post->post_type != 'featured-item')

            if ( !wp_verify_nonce( $_POST['sf_featured_noncename'], plugin_basename( __FILE__ ) ) )

            // Taxonomy meta
            update_post_meta( $post->ID, "link_to_post", $_POST["link_to_post"] );


else :
    exit("Class FeaturedItem already exists.");
endif ;

if (!isset($FeaturedItem)) {
    $FeaturedItem = new FeaturedItem();

if (isset($FeaturedItem)) {
    add_action( 'init' , array (&$FeaturedItem, 'register_featured_item' ));
    add_action( 'admin_enqueue_scripts', array (&$FeaturedItem, 'enqueue_scripts' ));
    add_action( 'admin_enqueue_scripts', array (&$FeaturedItem, 'enqueue_styles' ));
    add_action( 'admin_init' , array( &$FeaturedItem, 'register_featured_meta' ));
    add_action( 'save_post' , array( &$FeaturedItem, 'update_featured_meta' ));


The added complexity is something which you may not need - the link to the article. Looking back, I could've left this off entirely and simply used the rich text editor's built-in hyperlinking function.

The code for the front-end is quite simple too:

  • create a secondary post query (WP_Query)
  • retrieve posts of only type featured-item
  • limit the posts using posts_per_page => 3;
  • run <?php while ( $query->have_posts() ) : $query->the_post(); ?>
  • display posts, amend for formatting as necessary (the formatting on the code below is quite complex because I half-inched an aesthetic example on the internet which I liked the look of)


<?php $query = new WP_Query(
            'post_type' => 'featured-item',
            'posts_per_page' => 3,
            'meta_query' => array( array( 'key' => '_thumbnail_id' ) )

<div id="featured" >
        $li = ''; 
        $content = '';
        $count = 1;

    <?php while ( $query->have_posts() ) : $query->the_post(); ?>

        if ($count == 1) {
            $li .= '<li class="ui-tabs-nav-item ui-tabs-selected" id="nav-fragment-'. $count .'"><a href="#fragment-'. $count .'"><p>'. get_the_title() . '</p><span>'. get_the_content() .'</span></a></li>';
            $content .= '<div id="fragment-'. $count .'" class="ui-tabs-panel" style="">
                    '. get_the_post_thumbnail($post->ID, 'indexheader-thumb') .'
                     <div class="info" >
                        <p>'. get_the_title() . '</p>
                        <h2><a href="'. get_post_meta( $post->ID , 'link_to_post', TRUE ) .'" >'. get_the_content() .'</a></h2>
        } else {
            $li .= '<li class="ui-tabs-nav-item" id="nav-fragment-'. $count .'"><a href="#fragment-'. $count .'"><p>'. get_the_title() .'</p><span>'. get_the_content() .'</span></a></li>';
            $content .= '<div id="fragment-'. $count .'" class="ui-tabs-panel ui-tabs-hide" style="">
                    '. get_the_post_thumbnail($post->ID, 'indexheader-thumb') .'
                     <div class="info" >
                        <p>'. get_the_title() . '</p>
                        <h2><a href="'. get_post_meta( $post->ID , 'link_to_post', TRUE ) .'" >'. get_the_content() .'</a></h2>


    <?php endwhile; ?>

    <?php wp_reset_query(); ?>

    <ul class="ui-tabs-nav">
        <?php echo $li; ?>

    <!-- First Content -->
    <?php echo $content; ?>


If you want to attempt this, feel free to pester me for additional information.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.