WordPress 3.x boasts some powerful features for creating Custom Post Types, custom taxonomies, and custom meta boxes, bringing the popular and powerful blog platform another big step closer to a full Content Management System.

Get your hands dirty with plugin-building and theme customization to make these features fully functional on your site. I’ve provided a sample plugin and sample theme files to get the WordPress theme developer up and running with their own custom post types, taxonomies, and custom meta boxes.

Download File

KM-Custom-Post-Type-Template.zip (9 kb) Download KM-Custom-Post-Type-Template.zip

The downloadable zip file contains the following:

  • /plugin
    • cpt-template.php
  • /theme file examples
    • page-custom.php
    • single-cpt.php
    • taxonomy.php
  • README-KM-CPT-plugin.txt

Important: This is not meant to be a simple “plug-and-play” plugin. It is a developer’s tool, intended to be a jumping-off place for you to get started writing your own CPT plugins for use with your customized themes. In order to use this effectively, you should have intermediate to advanced experience in WordPress custom theme development.

  1. The plugin template file

    The plugin template file is called cpt-template.php. This plugin generates a generic Custom Post Type, a generic (hierarchical, ie. like categories) custom taxonomy for use with the CPT, and a generic custom meta box with custom fields. It is a template, and is meant to be edited / customized for your needs.

    • Open the cpt-template.php plugin file. The following is a simple overview of the file, not a complete tutorial: you will need to be prepared to examine the included files yourself.
    • The top section of the plugin file is the opening PHP tag, plugin information, and opening remarks. Note: If you have any duplicate functions between this plugin and other plugins or your theme’s functions.php file, activation of the plugin will fail. If this is the case, edit any existing functions to accommodate the ones in this file.
    • We’ll get started with the option to include post thumbnail support, like so:
      //-------------------------- FEATURED IMAGE SUPPORT
      if (function_exists('add_theme_support')) // Checks to see if your theme supports this
      	// name of the thumbnail, width, height, crop mode
      	set_post_thumbnail_size(150, 150, true);	// The thumbnail for archives, index, etc.
          add_image_size('featured-inpost', 400, 600, true);	// The thumbnail in the post, linking to the full-size image.
      // Generate link to full-size image
      function get_image_url( $image_id, $image_size ) {
          $image = wp_get_attachment_image_src( $image_id, $image_size );
          return $image[0];

      Some themes have post thumbnails already enabled.

    • Now we will initialize the function to create the Custom Post Type:
      //-------------------------- CUSTOM POST TYPE
      //-------------------------- http://codex.wordpress.org/Post_Types#Custom_Types
      add_action('init', 'create_cpt');
      function create_cpt()
      	// The labels your custom post type will use in the WordPress admin UI
      	$labels = array(
      		'name' => _x( 'Custom Post Type', 'custom post type' ),
      		'singular_name' => _x( 'Custom Post', 'cpt post' ),
      		'all_items' => __( 'All Custom Posts' ),
      		'add_new' => __( 'Add New' ),
      		'add_new_item' => __( 'Add New Custom Post' ),
      		'edit_item' => __( 'Edit Custom Post' ),
      		'menu_name' => _x( 'Custom Posts', 'custom posts' ),
      	// http://codex.wordpress.org/Function_Reference/register_post_type#Arguments
      	$args = array(
      		'labels' => $labels,	// Instructs use of the labels specified above
      		'description' => _x( 'A default custom post type for customizing.', 'Short descriptive summary of the post type' ),
      		'public' => true, 	// Meta argument to define default values for publicly_queriable, show_ui, show_in_nav_menus, exclude_from_search
      		'menu_position' => 5,	// 5 = Below posts (see WP codex for more information on menu position)
      		'hierarchical' => false,	// True: parent structure like pages | False: like posts
      		'supports' => array('title', 'editor', 'thumbnail','excerpt','revisions'),	// Alias for add_post_type_support()
      		'taxonomies' => array('category','post_tag'), // Shares these with other posts: needs a query function for archives to work!
      		'has_archive' => true,	// Enables custom post type archives: boolean or string (string will be the archive slug)
      		'rewrite' => true,	// Rewrite permalinks, use post type as slug
      	// http://codex.wordpress.org/Function_Reference/register_post_type
      		'cpt',	// internal name
      		$args 	// arguments from the array above

      The file contains detailed comments, but you should visit the WordPress Codex for additional information on the functions used.

    • Flush rewrite on activation so that permalinks work properly for Custom Post Types and their archives:
      add_action('init', 'create_cpt');
      function my_rewrite_flush() {
      register_activation_hook(__FILE__, 'my_rewrite_flush');
    • Share categories and/or post tags across post types:

      You will need to add your custom post type to the $post_type array.

    • Create a custom taxonomy for the Custom Post Type:
      //-------------------------- CUSTOM TAXONOMIES
      //-------------------------- http://codex.wordpress.org/Taxonomies#Custom_Taxonomies
      add_action( 'init', 'create_taxonomy', 0 );
      function create_taxonomy() 
      	// The labels that your custom taxonomy will use in the WordPress admin UI
          $labels = array(
          	'name' => _x( 'Custom Taxonomies', 'taxonomy general name' ),
      		'singular_label' => _x( 'Custom Taxonomy', 'taxonomy singular name' ),
      		'search_items' => __( 'Search Custom Taxonomies' ),
      		'all_items' => __( 'All Custom Taxonomies' ),
      		'parent_item' => __( 'Parent Custom Taxonomy' ),
      		'parent_item_colon' => __( 'Parent Custom Taxonomy' ),
      		'edit_item' => __( 'Edit Custom Taxonomy' ),
      		'update_item' => __( 'Update Custom Taxonomy' ),
      		'add_new_item' => __( 'Add New Custom Taxonomy' ),
      		'new_item_name' => __( 'New Custom Taxonomy Name' )
          // http://codex.wordpress.org/Function_Reference/register_taxonomy
      		'custom_taxonomy', // internal name
      		'cpt', // object type - what post type can use this taxonomy, can also be an array of post types: array( 'post', 'page')
      			// http://codex.wordpress.org/Function_Reference/register_taxonomy#Arguments
      			'labels' => $labels,	// Instructs use of the labels specified above
      			'hierarchical' => true // True: tree-structure like categories | False: free-form structure like tags  

      Custom taxonomies are ways to classify your custom posts. Categories and tags are taxonomies. Taxonomies can be classified as hierarchical (with a parent / tree structure like categories) or not hierarchical (with a free-form structure like tags).

    • Create a custom meta box to house custom fields for the Custom Post Type:
      add_action('add_meta_boxes', 'cpt_meta_box'); 
      function cpt_meta_box()
      		'cpt-meta-box',	// HTML ID
      		'Custom Meta Box',	// Title - displayed at the top of the meta box 
      		'cpt_meta_options', 	// Callback to the function (see below)
      		'cpt',	// Where the meta box should be displayed - associate with CPT (or 'post', 'page') 
      		'normal', // Placement in the admin UI - 'normal': below editor | 'side' in sidebar
      		'high'	// Priority places the box closer to the top('high'), normal position ('normal'), or bottom ('low')
      // Create the custom fields inside the meta box
      //--- Do not substitute custom fields for taxonomies.
      //--- If you're trying to add a lot of dropdown/radio/checkbox set custom fields throughout your custom posts, consider using taxonomies instead.
      //--- Utilize the native WordPress fields before making custom fields that essentially serve as duplicates.
      //--- For example, this CPT uses the Excerpt field instead of a "Description" textarea custom field.
      function cpt_meta_options( $post )
      	$values = get_post_custom($post->ID); 
      	$text = isset( $values['cm_textField'] ) ? esc_attr( $values['cm_textField'][0] ) : '';
      	$link = isset( $values['cm_textLink'] ) ? esc_attr( $values['cm_textLink'][0] ) : '';
      	$selected = isset( $values['cm_select'] ) ? esc_attr( $values['cm_select'][0] ) : '';
      	// http://codex.wordpress.org/WordPress_Nonces, http://codex.wordpress.org/Function_Reference/wp_nonce_field
      	wp_nonce_field( 'cm_nonce', 'meta_box_nonce' );
      	<p> <!-- sample text field input -->
      		<label for="cm_textField">Text Field</label>&nbsp;
      		<input type="text" id="cm_textField" name="cm_textField" value="<?php echo $text; ?>">
      	<p> <!-- sample text field input we will use as a link in the theme -->
      		<label for="cm_textLink">Link</label> &nbsp;
      		<input type="text" id="cm_textLink" name="cm_textLink" value="<?php echo $link; ?>">
      	<p> <!-- sample dropdown -->
      		<label for="cm_select">Dropdown Select</label>&nbsp;
      		<select id="cm_select" name="cm_select">
      			<option value="First Option" <?php selected( $selected, 'First Option' ); ?>>First Option</option>
      			<option value="Second Option" <?php selected( $selected, 'Second Option' ); ?>>Second Option</option>
      			<option value="Third Option" <?php selected( $selected, 'Third Option' ); ?>>Third Option</option>

      Custom fields are a way to enter and store customized meta data. Putting them in custom meta boxes is a clean, presentable way to enter additional information.

    • Save the Custom Fields data when saving the post:
      // Save the custom field data
      add_action('save_post', 'cpt_meta_box_save');
      function cpt_meta_box_save()
          global $post;
      	// If autosaving:
          if ( defined('DOING_AUTOSAVE') && DOING_AUTOSAVE ) {
      		return $post_id;
          } else {
              update_post_meta($post->ID, 'cm_textField', esc_attr( $_POST['cm_textField']) );
      		update_post_meta($post->ID, 'cm_textLink', esc_attr( $_POST['cm_textLink']) );
      		update_post_meta($post->ID, 'cm_select', $_POST['cm_select']);
    • Customize the Admin UI view for custom post types and taxonomies:
      //-------------------------- CUSTOM ADMIN UI COLUMNS
      add_filter('manage_edit-cpt_columns', 'cpt_edit_columns');
      function cpt_edit_columns($columns)
          $columns = array(
      		"cb" => "<input type="\&quot;checkbox\&quot;" />",
      		"title" => "Custom Post",
      		"textfield" => "Text Field",
      		"link" => "Link",
      		"taxonomy" => "Custom Taxonomy",
      	return $columns;
      // http://codex.wordpress.org/Plugin_API/Action_Reference/manage_posts_custom_column
      add_action("manage_posts_custom_column",  "cpt_custom_columns");
      function cpt_custom_columns($column)
      	global $post;
      	switch ($column) {
      		case "textfield":
      			$custom = get_post_custom();
      			echo $custom["cm_textField"][0];
      		case "link":
      			$custom = get_post_custom();
      			echo $custom["cm_textLink"][0];
      		case "taxonomy":
      			echo get_the_term_list($post->ID, 'custom_taxonomy', '', ', ','');
  2. Displaying custom posts, fields, and taxonomies on the front end

    Now that we’ve built a plugin to add and save information to the database, we need a way to display it on the actual site. You will need to create a customized loop for your CPT. There are many ways to do this, but using single-cpt.php may be the simplest method.

    • Showing custom taxonomies, similar to displaying Categories or Tags:
      <?php echo get_the_term_list( $post--->ID, 'custom_taxonomy', 'Custom Taxonomies: ',', ','
      ' ); ?>
    • Displaying the in-post thumbnail, with a link to view the full-size uploaded image:

      You can also use an else statement to show a default thumbnail if the post does not have one.

    • Display custom field values:
      <?php $text = get_post_custom_values('cm_textField'); foreach ( $text as $key =--> $value ) { echo "$value "; } ?>

      You can also use an if/else statement to show a default value if no values have been entered (or to hide the field completely if it is empty):

      	<?php $link= get_post_custom_values('cm_textLink');   	if($link[0] != ""){ ?>
      		<!-- There is a link -->
      		<a href="<?=$link[0]?>">Visit the Site</a>
              <?php }else{ ?>
      		<!-- There isn't a link -->
      		<em>Link Unavailable</em>
      	<?php } ?>
  3. The Custom Post Type archive page

    The system will generate an archive for you if you set has_archive to true, but this is actually not as useful as it could be. The problem is that though you can make an archive-{cpt}.php template in the theme, it’s difficult to use in a menu without hard-coding unless you have the appropriate taxonomies set up to add the archive to a dynamic menu.

    Therefore, it might be useful to make a page template that queries your CPT posts. This page will then be available to the pages menu. A sample page-custom.php is provided.

    /* Template Name: Custom Posts */  
    // set the $paged variable
    $paged = (get_query_var('paged')) ? get_query_var('paged') : 1;
    // Query to fetch the Custom Posts and display them 
    query_posts('post_type=cpt') . $paged

    Include the loop for an archive-type listing of posts on this page. You can then create a new page in WordPress using this page template and it will display your CPT archive. Make sure you do not name the page the same thing as your Custom Post Type slug, however, as this will cause 404 errors in pagination.

  4. The custom taxonomy archive

    Custom taxonomies do not generate their own archives the way custom post types do. You can create an archive page to accommodate custom taxonomies in the same way that themes like Twenty Ten and Twenty Eleven do for categories and tags. A sample custom taxonomy archive is included: taxonomy.php.

    • Get the taxonomy information by inserting this code below the header tag:
      <?php $term = get_term_by( 'slug', get_query_var( 'term' ), get_query_var( 'taxonomy' ) ); ?>
    • Print the archive term in the heading:
      <h1 class="page-title"><?php <br> // Displays the taxonomy term this archive is displaying
       printf( __( 'Archives: %s' ), '<span>' . $term->name . '</span>' );
    • Show description of the taxonomy term:
      <!-- The description for this taxonomy -->
      <?php echo term_description( '', get_query_var( 'taxonomy' ) ); ?>
    • It is important to flush the permalinks by going to the Permalinks page in your dashboard and re-saving to flush the rewrite settings. This prevents 404 errors when you attempt to view your taxonomy archives.

The KM-H5 Theme

The sample theme files included in this download are intended to integrate with my HTML5 bare-bones theme, which can be downloaded from this post: WordPress HTML5 Theme.

In Conclusion

I created these template files to make development of new custom post types much faster for myself in the future. I encourage you to use these files in the same way: to familiarize yourself with the process and to give yourself a head start on future WordPress development projects. It is important to note that this is not the only way to do this! There are many methods out there and I encourage you to experiment and find the ones that work best for you. To read more helpful information on different ways you can lay out and format your custom post types and custom taxonomies, see:

Shortcuts & Other Alternatives

While it’s great to know how to do CPTs, custom taxonomies, and custom meta boxes / fields manually, you can also use online tools and WP plugins to generate the pieces needed to get these features rolling. Now that you’ve done the nitty-gritty and (hopefully) understand the core functions and principles, here are some options for making the process a breeze:


I have revisited this topic in the post Custom Post Types, Revisited. Please see the article for additional information on methods of implementing custom post types, taxonomies, and custom meta information.