This post is dedicated to Boone Gorges (aka ‘WP Boone’ to me), who donated to help me get to WCSF. My brother is also named Boone, so even if WP Boone wasn’t so awesome, I’d like him.

Brain - You must useCustom Post Types. I really dig them, as a great way to make ‘kind of’ pages, without making a million pages. They don’t ‘order’ as well as pages, and default to publish date, but really that could be adjusted. The point, and I have one, is that they’re often a great alternative to Multisite, and I use them a lot.

There are lots of plugins that can make these for you, but I prefer to do it myself in a function file, becuase it gives me more flexibility for what is, let’s face it, a complicated sort of thing.

In this example, I’m going to make a ‘drawing’ custom post-type, like the one I just added to my photoblog. First I made a file called photo-cpt.php and in it put a header:

Drawing Name: Photos CPTs
Drawing URI:
Description: All Photos custom code.
Version: 1.0

Notice I am not telling you to put this in a functions file. I never put my CPT in my theme’s function, becuase I always make my CPTs able to work with any theme. By making it a stand-alone ‘plugin’ file, I can put it in mu-plugins and run it automatically. More on this in a minute.

The code itself is split into two sections. I learned this method from Justin Tadlock, who has a nice, if very techy, primer on Custom Post Types in WordPress. I freely admit, once I figured this code out, I saved it off line and copy/paste it where I need, replacing the terms (Drawing/s) for what the new CPT is.

	add_action( 'init', 'create_photos_post_types' );

	function create_photos_post_types() {

         /* Labels for the Drawing post type. */
        $drawings_labels = array(
                'name' => __( 'Drawings', $domain ),
                'singular_name' => __( 'Drawing', $domain ),
                'add_new' => __( 'Add New', $domain ),
                'add_new_item' => __( 'Add New Drawing', $domain ),
                'edit' => __( 'Edit', $domain ),
                'edit_item' => __( 'Edit Drawing', $domain ),
                'new_item' => __( 'New Drawing', $domain ),
                'view' => __( 'View Drawing', $domain ),
                'view_item' => __( 'View Drawing', $domain ),
                'search_items' => __( 'Search drawings', $domain ),
                'not_found' => __( 'No drawings found', $domain ),
                'not_found_in_trash' => __( 'No drawings found in Trash', $domain ),

        /* Arguments for the Drawing post type. */
        $drawings_args = array(
                'labels' => $drawings_labels,
                'capability_type' => 'post',
                'public' => true,
                'has_archive' => true,
                'can_export' => true,
                'query_var' => true,
                'rewrite' => array( 'slug' => 'drawings', 'with_front' => true ),
                'taxonomies' => array( 'post_tag', 'category'),
                'supports' => array( 'title', 'editor', 'excerpt', 'thumbnail', 'custom-fields', "photos-post-settings" ),

        /* Register the Drawing post type. */
        register_post_type( apply_filters( 'photos_drawings_post_type', 'drawings' ), apply_filters( 'photos_drawings_post_type_args', $drawings_args ) );

Looking at this, it’s actually surprisingly straightforward what I’m adding and where. The weird code of $domain is a variable I’ve defined elsewhere, and lets me translate if I need to. I probably won’t but it’s a good practice to get into. By splitting out my labels into their own variable, I’m able to break them up and make it more readable. As Otto says, good code doesn’t need inline documentation becuase it’s readable. You can see the names, and the fields, I’m adding, and they magically become self-explanatory. Then in my drawing arguments, I again make a variable with the settings. Pull in the complex labels, then I can break out the next arguments into something readable.

Trucks and Buses Must Use Low Gear You can read all the various options in the codex article for register_post_type(), which is the function I finally call at the end.

If you wonder why I have the whole thing wrapped in an action, it’s becuase in other places I actually add multiple post types to a site. This lets me put them all in one action, call it once, and walk away. As long as each CPT has a label, arguments, and registration, they’ll all run.

Below that action, I have one to add my CPT to my ‘right now’ section on the dashboard. I got this from James Laws over at WP Ninjas, and really it’s one of my favorite things.

// Adding to Right Now
	add_action( 'right_now_content_table_end', 'photos_right_now' );
	function photos_right_now() {
          // drawings
          $num_drawings = wp_count_posts( 'drawings' );
          $num_p = number_format_i18n( $num_drawings->publish );
          $text_p = _n( 'drawings', 'drawings', intval($num_drawings->publish) );
          if ( current_user_can( 'administrator' ) ) {
            $num_p = "<a href='edit.php?post_type=drawings'>$num_p</a>";
            $text_p = "<a href='edit.php?post_type=drawings'>$text_p</a>";
          echo "\n\t".'<tr class="first">';
          echo "\n\t".'<td class="first b b-drawings">' . $num_p . '</td>';
          echo "\n\t".'<td class="t drawings">' . $text_p . '</td>';
          echo "\n\t".'</tr>';


Taking a step back, there’s this interesting line in my arguments:

'taxonomies' => array( 'post_tag', 'category'),

All this does is say ‘I want to use post tags and categories in my CPT.’ And in this case, it’s the same ones as I use for my normal posts. You can do a lot more with it if you wanted, but I believe in KISS.

To loop back around, however, why do I put this in an mu-plugin? First and formost, it’s portable. No matter what theme I use, it comes with me. As I talk about this method in No Children Necessary, it really comes into play here more than anywhere else. On a single, traditional, WP install, I just toss it in and walk away. It’s code, I don’t want my end-users playing with it, so a non-editable file is perfect. For Multisite, I really just add if ( $blog_id == 2 ) { ... } around the whole thing. I could do it just on the actions, but this is easier for me. I can see right away ‘Oh! Site 2.’

This is obviously not going to work for everyone, but sometimes just looking at the next option will give you a new idea.

Reader Interactions


  1. You should not use a variable for the text domain; not all translation tools can handle that correctly. And, please, make the slug translatable too. 🙂

    The current_user_can( 'administrator' ) should probably be current_user_can( 'edit_drawings' )?

    • Given that I was never releasing this for translation, it wasn’t a concern here. If I was making a real plugin, I’d consider it.

      As for current_user_can… Yes, if you wanted to have anyone who could edit drawings see that status, you would do that. In this instance, not a concern, but easily changeable.

      Remember, all this is to get you started 😉

%d bloggers like this: