(My first WP question ever asked! Be gentle!)

I'm building a site that is mostly pages (i.e., static), using WP as CMS. At the bottom of several of the pages, there will appear 1, 2, or 3 "promo boxes" -- basically button-images that link to other parts of the site. Though only up to 3 promo boxes will appear on any given page, there will be ~30 different ones to choose from.

When my client creates a new page, I'd like him to be able to choose promo boxes from something like a dropdown list of all of the possible promo boxes.

Seems to me this should work like this:

  • Create a custom post type called "promo-box". (Though it could just as easily be a tag for regular posts.)
  • Use a tool like Custom Field Template to create a dropdown on the page editor, where the values of the dropdown options are dynamically generated from the list of all existing promo-box posts. (This is the part I don't know how to do.)
  • Access the resulting metadata (post number is really all I need, then I can get everything else) on the page template.

Based on responses to other questions here, I have taken initial looks at WPAlchemy MetaBox, Posts-2-Posts, and SLT Custom Fields, but I confess the documentation for each of them is slightly geekier than I am, so I haven't delved too deeply.

Advice? Is one of the above tools the right solution for me, and I just have to figure it out? Am I missing something here?

  • Wow, thanks for all of the support! I hope that I'm not in anyway devaluing MikeSchinkel's time and generosity, but I chose the WPAlchemy answer as the "official" answer. I'm still new enough to PHP/Wordpress that I'm not yet super comfortable with classes and hooks and static functions and such. I hope to someday be as proficient as y'all! Commented Dec 24, 2010 at 23:58

2 Answers 2


As the author of WPAlchemy, I'm a bit bias, but you essentially have a good working model outlined to follow depending on what ever route you choose.

However, if using WPAlchemy, you would basically do something like the following (step #2):

//  functions.php

include_once 'WPAlchemy/MetaBox.php';

if (is_admin()) 
    // a custom style sheet if you want to do some fancy styling for your form
    wp_enqueue_style('custom_meta_css', TEMPLATEPATH . '/custom/meta.css');

// define the meta box
$custom_metabox = new WPAlchemy_MetaBox(array
    'id' => '_custom_meta',
    'title' => 'My Custom Meta',
    'template' => TEMPLATEPATH . '/custom/meta.php'

custom/meta.css can contain styles that you can style your form with and custom/meta.php is essentially an HTML file with the FORM contents of the meta box, in this case your drop down, to generate your drop down you would do a custom wp query to get all your custom post types. WPAlchemy has some special helper functions to aid in creating your form elements.

There is additional documentation to assist you when working in the template.

The main goal of WPAlchemy was to keep control in the hands of the developer, from styling (look + feel) to meta box content definition.

And myself and others are always willing to help those who comment and ask questions.

  • 1
    Nice answer, but may i suggest hooking that extra stylesheet request specifically to the post editoring screen. Same may also be said for metabox creation, that ideally should be hooked onto do_meta_boxes with some conditional logic or alternatively onto add_meta_boxes_{%TYPE%}..
    – t31os
    Commented Dec 22, 2010 at 9:21

Hehe, you're a newbie! We're gonna rip ya to shreds...!

j/k :) We offer warm welcome to all newbies here, glad to have you.

So this is the 3rd time I've heard this requirement, twice from clients and not again from you (and your client.) That tells me it's a reasonably common need.

WordPress Custom Metabox showing Three (3) Dropdowns

I liked your analysis so I decided to code up a class to address your 2nd point. I called it LittlePromoBoxes because I can never get this song out of my head, thanks to them. Basically I use the class to encapsulate to otherwise avoid potential naming conflicts with the functions I'd need to write.

You can put this class in your theme's functions.php file or in a .PHP file of a plugin you might be writing (but don't worry, it looks a lot more complex than it is.)

The first function on_load() is a static function which I call at the end of the class declaration to initialize the three (3) hooks you'll need (fyi static functions are essentially functions related to the class, not the instance) :

  1. The init hook to register the promo-box post type,

  2. The add_meta_boxes_post hook to allow you to define the metabox, and

  3. The wp_insert_post_data hook to allow you to capture the selected promo boxes and save to the database.

Each of those hooks reference another static function in the class (these were the functions I was encapsulating by creating the class.)

I'll skip describing the action_init() function and my make_labels() helper function assuming you know how to register a post type based on you question.

The action_add_meta_boxes_post() function registers the metabox using the WordPress core function add_meta_box() and I've commented it's parameters to explain why I passed what I passed for each. The callback function the_little_promo_boxes_metabox() is of course another static function of the class and it is what actually displays the content in the metabox. It primarily uses the WordPress core function wp_dropdown_pages() to display a list of promo boxes (note that it will display other post types besides 'page' but only if they are marked as being 'hierarchical'=>true in their post type registration. Why only hierarchical? Because that's the way they wrote it, that's why! :)

Since we're showing three (3) dropdowns we need to give each a unique ID in the HTML ("promo_box_{$i}") but the same name with square brackets ('promo_boxes[]') so that PHP will collect them into an array inside $_POST variable (which WordPress accesses for us; you'll see how in a minute). And of course we need to set the selected value ((empty($promo_boxes[$i]) ? 0 : $promo_boxes[$i])) if indeed one of the values had previously been selected.

I also used the WordPress core function get_post_type_object() to show how to get the labels from a post type, and also using the WordPress core function get_post_meta() to retrieve an array of promo box IDs from using the custom field key '_promo_boxes' which I'll show you have to save next (note I used a preceding underscore in the name '_promo_boxes' which causes WordPress to hide from the standard custom field UI when the user is editing the post.).

The last function to describe before you see the code is filter_wp_insert_post_data() which receives the existing post data in the first parameter ($data) and the contents of the $_POST array thanks to WordPress as the second parameter ($postarr). Inside this function we call the WordPress core function update_post_meta() and extract the promo boxes array ($postarr['promo_boxes']) to save to the custom field value for the key '_promo_boxes' for the post specified by the $_POST array (i.e. $postarr['ID']).

That said, here's the code for the LittlePromoBoxes class:

class LittlePromoBoxes {
  static function on_load() {
  static function action_init() {
      'labels'          => self::make_labels('Promo Box','Promo Boxes'),
      'public_queryable'=> false,
      'hierarchical'    => true,  // IMPORTANT!!! wp_dropdown_pages() requires 'hierarchical'=>true
      'show_ui'         => true,
      'query_var'       => false,
      'supports'        => array('title','editor','thumbnail','custom-fields'),
  static function make_labels($singular,$plural=false,$args=array()) {
    if ($plural===false)
      $plural = $singular . 's';
    elseif ($plural===true)
      $plural = $singular;
    $defaults = array(
      'name'              =>_x($plural,'post type general name'),
      'singular_name'      =>_x($singular,'post type singular name'),
      'add_new'            =>_x('Add New',$singular),
      'add_new_item'      =>__("Add New $singular"),
      'edit_item'          =>__("Edit $singular"),
      'new_item'          =>__("New $singular"),
      'view_item'          =>__("View $singular"),
      'search_items'      =>__("Search $plural"),
      'not_found'          =>__("No $plural Found"),
      'not_found_in_trash'=>__("No $plural Found in Trash"),
      'parent_item_colon' =>'',
    return wp_parse_args($args,$defaults);
  static function action_add_meta_boxes_post($post) {
      'little-promo-boxes',   // Metabox Name, used as the "id" for a wrapping div
      'Little Promo Boxes',   // Metabox Title, visible to the user
      array(__CLASS__,'the_little_promo_boxes_metabox'), // Callback function
      'post',                 // Add to the Edit screen for Post Types of 'post'  
      'side',                 // Show it in the sidebar (if center then it would be 'normal'
      'low'                   // Show it below metaboxes that specify 'high'
  static function the_little_promo_boxes_metabox($post) {
    $pto = get_post_type_object('promo-box');
    $default_options = array(
      'post_type' => 'promo-box',
      'show_option_none' => "Select a {$pto->labels->singular_name}",
    $promo_boxes = get_post_meta($post->ID,'_promo_boxes',true);
    for($i=0; $i<=2; $i++) {
        'id'       => "promo_box_{$i}",
        'name'     => 'promo_boxes[]',
        'selected' => (empty($promo_boxes[$i]) ? 0 : $promo_boxes[$i]),
  static function filter_wp_insert_post_data($data, $postarr) {
    return $data;
  static function get_promo_boxes($post=false) {
    static $promo_boxes=array();
    if (!$post)
      $post = $GLOBALS['post'];
    if (!isset($promo_boxes[$post->ID])) {
      $promo_boxes[$post->ID] = get_post_meta($post->ID,'_promo_boxes',true);
      $index = 0;
      foreach($promo_boxes[$post->ID] as $promo_box_id) {
        $promo_boxes[$post->ID][$index++] = (is_numeric($promo_box_id) ? get_post($promo_box_id) : false);
    return $promo_boxes[$post->ID];
  static function get_promo_box($number,$post=false) {
    $promo_boxes = self::get_promo_boxes($post);
    return $promo_boxes[$number-1];

There are still two (2) static functions not yet mentioned: get_promo_boxes() and get_promo_box(); these are helper functions to help you retrieve the posts of post_type='promo-box' by their ordinal numbers 1..3. But to make them more WordPress like here are two wrapper functions to add to your theme's functions.php file (note that you can pass a post as a parameter but you don't have to unless you are using a different post that the one in The Loop):

function get_little_promo_boxes($post=false) {
  return LittlePromoBoxes::get_promo_boxes($post);
function get_little_promo_box($number,$post=false) {
  return LittlePromoBoxes::get_promo_box($number,$post);

Now you can call one or both of these functions in your single.php theme file with code that might look like this (this code could have been written in a loop but most WordPress themers seem to like to duplicate code so they can read it instead of eliminating redundancy. So, when in Rome...):

  $promo_boxes = get_little_promo_boxes();
  if (isset($promo_boxes[1]))
    echo '<div id="promo-box1" class="promo-box">' . get_the_title($promo_boxes[1]->ID) . '</div>';
  if (isset($promo_boxes[2]))
    echo '<div id="promo-box2" class="promo-box">' . get_the_title($promo_boxes[2]->ID) . '</div>';
  if (isset($promo_boxes[3]))
    echo '<div id="promo-box3" class="promo-box">' . get_the_title($promo_boxes[3]->ID) . '</div>';
  • 1
    You never cease to amaze me with your answers, the effort you go through to create the code, and explain each step.. awesome!.. (your mention of little boxes also has me thinking about one of my favorite TV series)..
    – t31os
    Commented Dec 22, 2010 at 13:06
  • 1
    @t31os - Thanks! When I get started answering I just can't stop myself. Obsessive/compulsive I guess. But at least I'm putting to good use! Commented Dec 22, 2010 at 13:18
  • @toscho - Thanks. Yes, I so rarely add in humor that when it comes to me I can't resist. :-) Commented Dec 23, 2010 at 23:03

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.