Menu Search
Jump to the content X X
Smashing Conf Barcelona

You know, we use ad-blockers as well. We gotta keep those servers running though. Did you know that we publish useful books and run friendly conferences — crafted for pros like yourself? E.g. our upcoming SmashingConf Barcelona, dedicated to smart front-end techniques and design patterns.

The Complete Guide To WordPress Custom Post Types

WordPress has been gaining a foothold in the general content management system (CMS) game for a few years now, but the real breakthrough was the custom post type mechanism which allows for the creation of a wide variety of content. Let’s take a look at how this came to be and all the options that this great functionality offers.

wordpress custom post type1
Some of the custom post types you can create in WordPress.

What It Used To Be Like Link

In practice, custom post types have been around for a long time, more specifically since February 17, 2005, when WordPress 1.5 added support for static pages, creating the post_type database field.

Further Reading on SmashingMag: Link

The wp_insert_post() function has been around since WordPress 1.0, so when the post_type field was implemented in 1.5, you could simply set the post_type value when inserting a post. Of course, creating and managing custom post types required much more than that, but the amount of coding needed became less and less as WordPress functions became more and more flexible.

By version 2.8, the register_post_type() function and some other helpful things were added to the nightly builds, and when 2.9 came out, the functions became available to everyone. At this point, extensive coding and hacks were not needed to make WordPress a full blown CMS; you could use plenty of great built-in functions to make WordPress do your bidding.

What WordPress Can Do For You Now Link

A custom post type is nothing more than a regular post with a different post_type value in the database. The post type of regular posts is post, pages use page, attachments use attachment and so on. You can now create your own to indicate the type of content created. You could create custom post types for books, movies, reviews, products and so on.

If created correctly, you can achieve the following with a few lines of code:

  • The custom post type will show up in the back end as a separate menu item with its own post list and “add new” page
  • Navigating to will take you to the archive page for the post type. This is akin to visiting the front page for latest posts from the “post” post type.
  • Categories and tags can be made available to the custom post type, or you can create custom taxonomies.

Apart from these, you can modify countless options, such as where the custom post type should be placed in the menu, should it be searchable, which user level can access it, should it be hierarchical, custom rewrite rules, etc.

Different types of content have different data requirements. For regular posts, you’ll want to specify the author, category, date and so on. For a “book” custom post type, ideally you’d like to have the option to specify the book’s author, the page count, genre, publisher and other book-specific data. Using custom meta boxes, this is easily achieved and managed as well.

Custom meta boxes156 enable you to add additional boxes to the edit screen of a post. They usually use custom fields, so you could just use custom fields as well, but by separating out some custom fields as meta boxes, you can create a much smoother and usable admin.

Working With Custom Post Types Link

To effectively create and use custom post types, you’ll need to be familiar with the following:

  • Creating custom post types,
  • Creating custom taxonomies,
  • Creating custom meta boxes.

Creating Custom Post Types Link

First on our agenda is creating the post type itself. Ideally you should create a plugin7 when working with custom post types, but if you don’t know how, or just need a quick test, you can use the functions.php file in your theme.

function my_custom_post_product() {
  $args = array();
  register_post_type( 'product', $args ); 
add_action( 'init', 'my_custom_post_product' );

In its simplest form, it will create a post type which has almost no customization. It won’t be public, it won’t show up in the admin, interaction messages will be the same as posts (“post saved,” “post updated,” etc.) and so on. To tailor our new post type to our needs, I’ll go through some of the more frequently-used options and add them to the previously empty $args array.

function my_custom_post_product() {
  $labels = array(
    'name'               => _x( 'Products', 'post type general name' ),
    'singular_name'      => _x( 'Product', 'post type singular name' ),
    'add_new'            => _x( 'Add New', 'book' ),
    'add_new_item'       => __( 'Add New Product' ),
    'edit_item'          => __( 'Edit Product' ),
    'new_item'           => __( 'New Product' ),
    'all_items'          => __( 'All Products' ),
    'view_item'          => __( 'View Product' ),
    'search_items'       => __( 'Search Products' ),
    'not_found'          => __( 'No products found' ),
    'not_found_in_trash' => __( 'No products found in the Trash' ), 
    'parent_item_colon'  => '',
    'menu_name'          => 'Products'
  $args = array(
    'labels'        => $labels,
    'description'   => 'Holds our products and product specific data',
    'public'        => true,
    'menu_position' => 5,
    'supports'      => array( 'title', 'editor', 'thumbnail', 'excerpt', 'comments' ),
    'has_archive'   => true,
  register_post_type( 'product', $args ); 
add_action( 'init', 'my_custom_post_product' );
  • labels
    The labels option should be an array defining the different labels that a custom post type can have. I have separated this out above just to make the arguments for registering a post type clearer.
  • description
    A short explanation of our custom post type; what it does and why we’re using it.
  • public
    This option controls a bunch of things in one go. Setting this to true will set a bunch of other options (all to do with visibility) to true. For example, it is possible to have the custom post type visible but not queryable. More on this later.
  • menu_position
    Defines the position of the custom post type menu in the back end. Setting it to “5” places it below the “posts” menu; the higher you set it, the lower the menu will be placed.
  • supports
    This option sets up the default WordPress controls that are available in the edit screen for the custom post type. By default, only the title field and editor are shown. If you want to add support for comments, revisions, post formats and such you will need to specify them here. For a full list take a look at the arguments section8 in the Codex.
  • has_archive
    If set to true, rewrite rules will be created for you, enabling a post type archive at (by default)
A custom post type in the menu.
A custom post type in the menu.

After setting this up, you should see the menu entry for the custom post type. You should be able to add posts, view the post list in the admin and also visit the published posts on the website.

As I mentioned there are a lot of things you can modify when creating a post type. I suggest looking at the arguments list in the Codex for a full description of each option and the possible values.

Custom Interaction Messages Link

WordPress generates a number of messages triggered by user actions. Updating, publishing, searching, etc., in the back end all lead to messages which — by default — are tailored to regular posts. You can change the text of these messages easily by using the post_updated_messages hook.

function my_updated_messages( $messages ) {
  global $post, $post_ID;
  $messages['product'] = array(
    0 => '', 
    1 => sprintf( __('Product updated. <a href="%s">View product</a>'), esc_url( get_permalink($post_ID) ) ),
    2 => __('Custom field updated.'),
    3 => __('Custom field deleted.'),
    4 => __('Product updated.'),
    5 => isset($_GET['revision']) ? sprintf( __('Product restored to revision from %s'), wp_post_revision_title( (int) $_GET['revision'], false ) ) : false,
    6 => sprintf( __('Product published. <a href="%s">View product</a>'), esc_url( get_permalink($post_ID) ) ),
    7 => __('Product saved.'),
    8 => sprintf( __('Product submitted. <a target="_blank" href="%s">Preview product</a>'), esc_url( add_query_arg( 'preview', 'true', get_permalink($post_ID) ) ) ),
    9 => sprintf( __('Product scheduled for: <strong>%1$s</strong>. <a target="_blank" href="%2$s">Preview product</a>'), date_i18n( __( 'M j, Y @ G:i' ), strtotime( $post->post_date ) ), esc_url( get_permalink($post_ID) ) ),
    10 => sprintf( __('Product draft updated. <a target="_blank" href="%s">Preview product</a>'), esc_url( add_query_arg( 'preview', 'true', get_permalink($post_ID) ) ) ),
  return $messages;
add_filter( 'post_updated_messages', 'my_updated_messages' );
A custom message after saving a product.9
A custom message after saving a product.

As you can see, this is not the most user-friendly method of managing messages. An associative array would be far better; we could see what each message is for without having to read the actual message.

Notice that you can change the messages for all custom post types using this single function. The $messages array holds the messages for all post types, so you can modify them all here. I personally create a function for each post type just so I can group the post type creation and the custom messages together easily.

Contextual Help Link

A feature I rarely see implemented is the customized contextual help. As a user, I’ve never actually used this feature myself, but I’m sure that many people do; in any case, it’s nice to provide some hand-holding for less experienced users.

The contextual help feature is a descending tab which can be seen in the top right of pages where available. Let’s take a look at how the contents can be changed.

function my_contextual_help( $contextual_help, $screen_id, $screen ) { 
  if ( 'product' == $screen->id ) {

    $contextual_help = '<h2>Products</h2>
    <p>Products show the details of the items that we sell on the website. You can see a list of them on this page in reverse chronological order - the latest one we added is first.</p> 
    <p>You can view/edit the details of each product by clicking on its name, or you can perform bulk actions using the dropdown menu and selecting multiple items.</p>';

  } elseif ( 'edit-product' == $screen->id ) {

    $contextual_help = '<h2>Editing products</h2>
    <p>This page allows you to view/modify product details. Please make sure to fill out the available boxes with the appropriate details (product image, price, brand) and <strong>not</strong> add these details to the product description.</p>';

  return $contextual_help;
add_action( 'contextual_help', 'my_contextual_help', 10, 3 );

This is also a bit difficult because you have to know the ID of the screen you are on. If you print out the contents of the $screen variable, you should be able to determine the ID easily. This is also a function you can use to modify the contextual help of all custom post types at once, but I personally recommend grouping this together with the previous two blocks and only using it for one custom post type at a time.

Overview Link

To quickly recap, we used three functions to create a “complete” custom post type. We used register_post_type() to create the post type itself and two hooks — contextual_help and post_updated_messages — to create helpful guidance and relevant messages respectively.

Custom Taxonomies Link

Your regular blog posts use categories and tags to create an organization structure. However, the same organization doesn’t necessarily make sense for custom post types. Your blog posts could be about your “Life,” your “Thoughts” or your “Dreams.” These are obviously not appropriate for products.

This is the problem that drove developers to create custom taxonomies. You can create a separate taxonomy named “Product Categories” to house categories you only use for products. Kevin Leary wrote a great article about custom taxonomies in WordPress10 which I highly recommend, so I will only go into minor detail here.

function my_taxonomies_product() {
  $args = array();
  register_taxonomy( 'product_category', 'product' $args );

add_action( 'init', 'my_taxonomies_product', 0 );

Similarly to custom post types, you can create a taxonomy very easily, but you need to work at it a bit to tailor it to your needs. Custom taxonomies behave a bit better out of the box as they are public by default, so the above is actually enough to tie this taxonomy to the product posts. Let’s look at a customized example.

function my_taxonomies_product() {
  $labels = array(
    'name'              => _x( 'Product Categories', 'taxonomy general name' ),
    'singular_name'     => _x( 'Product Category', 'taxonomy singular name' ),
    'search_items'      => __( 'Search Product Categories' ),
    'all_items'         => __( 'All Product Categories' ),
    'parent_item'       => __( 'Parent Product Category' ),
    'parent_item_colon' => __( 'Parent Product Category:' ),
    'edit_item'         => __( 'Edit Product Category' ), 
    'update_item'       => __( 'Update Product Category' ),
    'add_new_item'      => __( 'Add New Product Category' ),
    'new_item_name'     => __( 'New Product Category' ),
    'menu_name'         => __( 'Product Categories' ),
  $args = array(
    'labels' => $labels,
    'hierarchical' => true,
  register_taxonomy( 'product_category', 'product', $args );
add_action( 'init', 'my_taxonomies_product', 0 );

As you can see, not much has changed. We added some labels and set the hierarchical option to true. This enables “category style” taxonomies. When set to false (this is the default), your taxonomy will be like the default tags.

There are a few other power options available which you can read about in Leary’s article, or you can go to the Codex entry on register_taxonomy()11.

The Product Categories custom taxonomy.12
The Product Categories custom taxonomy.

Post Meta Boxes Link

Meta boxes are the draggable boxes you see in the WordPress edit screen for a post. There are numerous built-in meta boxes like the publishing controls, the taxonomies, the author box, etc., but you can create some for yourself.

Meta boxes tend to be used to manage custom field data in a much more user-friendly way than the built-in custom fields box does. Since you put the controls in place, you can add client-side error checking and many other fancy things.

Justin Tadlock wrote the all-encompassing custom meta box article13 here on Smashing Magazine, which is a great in-depth article on the subject. I recommend reading it for the full picture, but I’ll get you started here.

Creating a meta box requires three steps:

  • Define the box itself,
  • Define the content of the meta box,
  • Define how the data from the box is handled.

Defining the Meta Box Link

add_action( 'add_meta_boxes', 'product_price_box' );
function product_price_box() {
        __( 'Product Price', 'myplugin_textdomain' ),

The code above creates the meta box with the following parameters (in the order given):

  • The unique identifier for the meta box (it does not have to match the function name),
  • The title of the meta box (visible to users),
  • The function which will display the contents of the box,
  • The post type the meta box belongs to,
  • The placement of the meta box,
  • The priority of the meta box (determines “how high” it is placed).

Defining the Content of the Meta Box Link

function product_price_box_content( $post ) {
  wp_nonce_field( plugin_basename( __FILE__ ), 'product_price_box_content_nonce' );
  echo '<label for="product_price"></label>';
  echo '<input type="text" id="product_price" name="product_price" placeholder="enter a price" />';

This is a simple box which only contains the price, so we have created a label and an input to manage it. A nonce14 field is also present, which adds security to the data submission.

Handling Submitted Data Link

In most cases, you will want to save the data as a custom field, but you are by no means restricted to this method. You could use the input to make a third party API call, to generate an XML file or whatever you like. The most common use is saving custom post data, so let’s take a look at how that is done.

add_action( 'save_post', 'product_price_box_save' );
function product_price_box_save( $post_id ) {

  if ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE ) 

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

  if ( 'page' == $_POST['post_type'] ) {
    if ( !current_user_can( 'edit_page', $post_id ) )
  } else {
    if ( !current_user_can( 'edit_post', $post_id ) )
  $product_price = $_POST['product_price'];
  update_post_meta( $post_id, 'product_price', $product_price );

The biggest part of this function is all about safety. First of all, if an autosave is being performed, nothing happens because the user hasn’t actually submitted the form. Then the nonce is checked, followed by permission checking. If these are all passed, we take our data and add it to the post using the update_post_meta() function.

An example of a meta box for an apartment post type.
An example of a meta box for an apartment post type.

Displaying Your Content Link

While there a plenty of nuances to all of the above, you should be familiar with the basics. All that is left is to actually use the data we now have and show things to the user. This involves showing posts — perhaps from various custom post types and taxonomies — and using our post metadata.

Displaying Posts Link

If you’ve created a post type with the has_archive parameter set to “true,” WordPress will list your posts on the post type’s archive page. If your post type is called “books,” you can simply go to and you’ll see your post list.

This page uses archive-[post_type].php for the display if it exists (archive-books.php in our case). If it doesn’t exist, it will use archive.php and if that doesn’t exist it will use index.php.

Another way to display custom post type content is to use a custom query with the WP_Query class. To display posts from a certain post type and custom taxonomy, you could do something like this:

    $args = array(
      'post_type' => 'product',
      'tax_query' => array(
          'taxonomy' => 'product_category',
          'field' => 'slug',
          'terms' => 'boardgames'
    $products = new WP_Query( $args );
    if( $products->have_posts() ) {
      while( $products->have_posts() ) {
          <h1><?php the_title() ?></h1>
          <div class='content'>
            <?php the_content() ?>
    else {
      echo 'Oh ohm no products!';

Displaying Metadata Link

Metadata can be retrieved easily using the get_post_meta() function. In our example above, we saved a post meta field named product_price. We can retrieve the value of this field for a given post using the following code:

  // If we are in a loop we can get the post ID easily
  $price = get_post_meta( get_the_ID(), 'product_price', true );

  // To get the price of a random product we will need to know the ID
  $price = get_post_meta( $product_id, 'product_price', true );

Final Words Link

As you can see, creating a CMS is pretty easy due to the modular nature of WordPress controls and functions. The methods outlined here can be used extremely well to create customized admins for nearly anything you can think of.

Since defining the content of meta boxes156 is completely up to you, you have the power to create additional features for your controls, making you or your clients very happy.

You can take this one step further with custom admin pages and completely custom content, but that’s a story for another day.

Image source16 of picture on front page.


Footnotes Link

  1. 1
  2. 2
  3. 3
  4. 4
  5. 5
  6. 6
  7. 7
  8. 8
  9. 9
  10. 10
  11. 11
  12. 12
  13. 13
  14. 14
  15. 15
  16. 16

↑ Back to top Tweet itShare on Facebook

Hallo, my name is Daniel :) I build plugins, themes and apps - then proceed to write or talk about them. I contribute to various other online sites. When not coding or writing you'll find me playing board games or running with my dog. Drop me a line on Twitter or visit my personal website.

  1. 1

    Pedro Costa Neves

    November 8, 2012 5:31 am

    You should use “_product_price” instead of “product_price”, it’s WordPress pattern for custom field meta :) and it don’t appear to the user mess up with the data inside the default “Custom Field” widget in post/page/custom post type area.

    • 2

      Yes, when creating a meta_box, I always do this as well. There is no reason to confuse the user if you don’t have to. One UI is good enough :)

    • 3

      Yes, good idea. It’s more better and hidden from Custom Fields

    • 4

      Hi Pedro!

      That’s actually a great point, thanks! I’ve been neck deep in writing a huge WordPress framework which gives you much better control over custom fields (among other things) and the custom fields are hidden by default to make room for the custom control. In this case I needed to use the underscore-less way to make sure that if it is deactivated the users still see the data :)

      I agree though, in most cases when using metaboxes, prefixing with an underscore is the way to go!

    • 5

      Great point Pedro! Keeping it more in-line with core is best. I know at Skematik – – we’ve tried to keep things very similar and encourage devs to do the same with custom field meta.

  2. 6

    Great read. It would have been nice if you recommended some cool classes that eases up the Meta Box issue like “Custom Meta Box” class on Github by Jared, Bill Erickson, and Andrew Norcross.

    This will make your life super easy, and keep you from going the long route of adding custom meta boxes.

    I love Smashing Magazine because it usually offers additional resources towards the end of the article for those who want to learn more about the topic.

    Another thing that I wrote about yesterday which could be useful is to debate whether the custom post type should go in your theme’s functions.php file or a plugin. I see that the article takes a stance that it should go in the plugins, but doesn’t elaborate much of the reasoning.

    Overall a job well done on this guide :)

    • 7

      Hi Syed, thanks for the comment!

      I should have looked into tools like that, good point! however, I think creating meta boxes is already super easy, and if you want to be a good developer you need to learn how to do it without helpers anyway :)

      Well done plugging in your site in a useful way ;) The reason I didn’t join in the debate is that it really isn’t something people reading this article should be worrying about. Also, in many situations you have no choice but to use the functions file.

      In a perfect world everything like this would be in plugins. However, we sell numerous high quality WordPress themes where we want to add tons of functionality but we need to do it all the themes files.

      My opinion is that if your code is clear, readable and modular it doesn’t make a lot of difference where you put your custom post type.

  3. 8

    Gijs Jorissen

    November 8, 2012 5:44 am


    Nice article. Maybe you can have a look at my helper. It makes this process much easier!


  4. 9

    Do not trust your user, escape or sanitize the inputs before display/save. A sanitize_text_field and a esc_attr have to be used ;)

  5. 12

    Two things to say:

    Advanced Custom Fields and WCK Post Type Creator – (Once you’ve got tired of doing the hard way!!!).

    • 13

      @Adam: yes, ACF is awesome, especially with the add-ons.
      Although you may run into difficulties when using a multi-language site and use the WPML plugin for that.

      Types and Views does a much better job here, for the non-coders.

  6. 15

    Isaac Shapira

    November 8, 2012 7:53 am

    Just more evidence that wordpress is very gradually becoming drupal.

  7. 16

    Gabriele Romanato

    November 8, 2012 9:19 am

    It’s worth mentioning that if the custom slug/permalink structure of your newly created post types doesn’t match the base permalink structure of your site, WordPress can raise a 404 error. In this case a common-sense solution is to change your permalink structure to match the custom post type structure and the restore it to normal. Another solution is to follow the approach used by Custom Post Type UI:

  8. 17

    Geert van der Heide

    November 8, 2012 10:39 am

    WordPress needs to make up its mind whether it wants to be strictly a blogging platform or an all purpose CMS. The developers are slowly adding functionality signifying the latter, and many developers treat it as such, but for an allround CMS it’s sorely lacking in features. Like being able to set up custom post types with taxonomies and custom fields from the interface of the control panel itself. Having to code this by hand or resort to plugins like Pods is, in my opinion, quite sad for a CMS this size, that has been around as long as it has. If they’re offering this functionality, then why not build a “post type configuration” area for it in the admin?

    WordPress might have a lot of plugins available that can meet almost any need, but it also relies on them heavily for offering a complete solution for any web project that is more than a traditional blog. And many plugins have a very different approach, look and feel compared to WordPress itself, meaning it doesn’t always integrate well.

    So I’m a bit confused: Please either go full CMS, or keep it a blogging platform. But no more of this half-baked functionality please :)

    • 18

      @ Geert: Yes I very much agree with this. When using Drupal I’m so used to having a UI in core which deals with custom content types, fields and taxonomies that I find it amazing that WordPress still doesn’t have this. As you say, if WP is going to be an all purpose CMS then I think it does need something like this.

      Yes, there are plugins that can do this but these have their problems too. I recently took over support of a WP site that used the very old non maintained plugin ‘Flutter’ to create custom write panels. The site was originally built before WP 3 launched. Because their site was so dependent on Flutter it couldn’t automatically be upgraded without the site breaking. Therefore it was constantly getting hacked :-(

  9. 19


    First off, great article – I’ve learned a lot.

    I need to create a carousel on the landing page of a website I’m working on. Do you think a custom post type would be a good way to handle the management of the slides or is there maybe an easier way to this through the use of WordPress’ media management? If a custom post type is the way to go then how would you handle the managing the custom fields for the slide (jpg, png, etc) upload?

    • 20

      Hi Reece,

      In our Themeforest themes we offer the same functionality and we use a mix of options. One thing you can do is to use the featured image of a custom post as the slide image. You can use the custom fields for the post to hold the data.

      You can also add custom fields to images directly, take a look at this post:

      You can also use all images uploaded to a post, or all featured images from a given category, although if you do you will need to use the post meta option above.

      If you don’t need to show these things to the user, you can simply use add_post_meta and get_post_meta on the image (since it’s actually a post) directly, without a UI.

  10. 22

    Hi nice article,

    But I have a question, after create the custom post types, the custom post loop and single custom post …

    how do you do to show the link of access in the navbar or sidebar? like in your website.

    could be with a custom link created in admin>dashboard>appearance>custom link>add to menu.

    could you make an example? please.


    • 23

      In the top menu I simply have a custom menu. If you have a “Books” custom post type, just create a link to and it will work fine. Make sure that ‘has_archive’ is set to true when registering the post type.

      In the sidebar I think that the widget is just a custom loop I made using WP_Query and by extending the WP_Widget class. That is a little too long to elaborate here, but an article is coming at some point ;)

  11. 24

    i have been searching for so long this type of article…great job:)

  12. 25

    Handy stuff, thanks Daniel P.

    Comment: While I understand that time / (page) space is often limited, I’m with Syed Balkhi (see comment above) and would like to read more on why and when than how. Most of this article (how) is in the WP Codex or found elsewhere. For example, (I believe) the mighty Justin Tadlock covered custom post types in a couple thorough blog articles some time back. What’s not always clear, especially to those interested in Custom Posts 101, is why and when a CP might be smart. But there doesn’t seems to be much WP blogging with that in mind.

    Question: Are taxonomies usable across multiple post types? That is, can I use the same category taxonomy in my own custom post type such that when there’s a (traditional) post of category X, I can also pull content from my custom post also assigned category X? A simple example might be an article about X (traditional post) with photos about X in the sidebar. The custom post would store the photos.

    Thanks again.

    • 26

      Hi Mark,

      Perhaps I will cover that in an upcoming article :) I do see where you’re coming from.

      For the taxonomies question, the answer is yes, absolutely. You would need to do the coding of course, but this is easily done. If you are on a single post page the display of the single post is a trivial matter, you can just use the regular ol’ loop. In the sidebar, you can detect which taxonomy term is being shown and create a custom loop, listing only photos from it.

      This is why custom taxonomies are extremely powerful. In the same way that you can use tags and categories for any custom content, you can use custom taxonomies for built in, and custom post types.

  13. 27

    Thanks for the great article Daniel.

    Wondering if I could test yours and your readers skills a bit with a dilema I’ve run into?

    When displaying Custom Post Type (CPT) data, any idea how to order it using a value from postmeta?
    To make matters trickier, I need to do so WITHIN the loop for another CPT!!!

    Here’s the scenario I am facing:

    * Working on a site for a conference.
    * Created a CPT for the Program Schedule, to hold entries such as breaks, registration times, special events, etc.
    * Some of the items in the Schedule, such as Workshops, have their OWN secondary CPT because of the extra data they need to store.
    * I can successfully query the Program Schedule CPT and received the correct post data, and loop through that with no troubles
    * IF, while looping through these post results, an entry is found such as Workshops that has it’s own secondary CPT data, a second query needs to be performed to retrieve this data, loop through it (while still within the bigger post loop) and display this secondary CPT data.
    * Some of these secondary CPT’s have postmeta fields for dates/times, which need to be used for ordering this content

    So the dilema is, how to perform a Custom Post Type query while WITHIN the loop for another Custom Post Type query and order the display of this secondary data based on a postmeta field ?

    My main query for the Program Schedule is setup exactly how you have shown in your article, by passing an array of arguments to the query constructor ala:

    $program_schedule = new WP_Query( $args );

    then looping through that content as per standard WordPress:

    if( $program_schedule->have_posts() ) {
    while( $program_schedule->have_posts() ) {

    Using this same format for the secondary inner CPT queries within the above loop wasn’t working, so I’ve been trying a different approach for those.

    So my secondary query so far is:

    $SQL = ”
    SELECT $wpdb->posts.*, $wpdb->postmeta.*
    FROM $wpdb->posts, $wpdb->postmeta
    WHERE $wpdb->posts.ID = $wpdb->postmeta.post_id
    AND $wpdb->posts.post_status = ‘publish’
    AND $wpdb->posts.post_type = ‘workshop’
    AND $wpdb->posts.post_date postmeta.meta_key = ‘session_day’
    AND $wpdb->postmeta.meta_value = %s

    $workshops = $wpdb->get_results( $wpdb->prepare( $SQL, $date ), OBJECT );

    The variable $date is being retreived from the URL, and is properly processed and sanitized to match the date field for the Workshop CPT;

    How do I add an ORDER BY clause to the above query to sort the results using the Workshop CPT meta value for the meta key ‘session_time’ ?

    HUGE thanks in advance for any and all help with regards to this matter.

    Your time, effort, and talent will be greatly appreciated.

    • 28

      Hi Br3nt!

      That’s a great question, I’m sure many people are facing it :) I will try and answer how to do this the way you want to, but I would reconsider using a custom post type for the secondary loop, since there is no real need for it. You can manage this data easily with well built custom meta box in the backend of the schedule items.

      Here is a quick screengrab of one of our premium themes: This particular post type is for Apartments, and what you’re seeing is the meta section for it. All the data in here gets saved to postmeta. If there is something like the seasonal pricing (multiple fields, multiple sets of fields possible) it can all be passed to update_post_meta() as an array. It will be serialized before it is saved. If you retrieve the data with get_post_meta() it will be unserialized for you automatically.

      If you absolutely must use multiple queries it is best to stay with WP_Query for both. The way you can query and order based on post meta is this:

      $args = array(
      ‘post_type’ => ‘workshop’,
      ‘post_status’ => ‘publish’,
      ‘orderby’ => ‘meta_value’,
      ‘meta_key’ => ‘session_day’,
      ‘meta_query’ => array(
      ‘key’ => ‘session_day’,
      ‘value’ => ”,
      ‘compare’ => ‘!=’,
      $my_posts = new WP_Query( $args );

      The reason I have meta_key and meta_query in there is that the codex states that to order by meta_value the meta_key parameter must be present. The meta_query functionality was added later so it may be able to replace meta_key in this regard as well, but I can’t check right now :)

      That should take care of problem one, now for multiple nested loops. The only thing to keep in mind here is to store all the data from outer loops in temporary variables. (A pastebin version is available here:

      $schedule= new WP_Query( array( ‘post_type’ => ‘schedule’ ) );
      if( $schedule->have_posts() ) {
      while ($schedule->have_posts()) {
      the_title( ”, ” );
      $temp_post = $post;
      $workshops = new WP_Query( array(‘post_type’ => ‘workshop’) );
      if( $workshop->have_posts() ) {
      echo ”;
      while($workshops->have_posts()) {
      the_title( ”, ” );
      echo ”;
      $post = $temp_post;

      I hope that answered your question :)

  14. 29

    Great article and examples! thanks Daniel.

  15. 30

    This is a great article. I did have to go through it a few times. I’m still working on understanding the complexities of creating custom posts, but this really help me walk through it.

  16. 31

    Good write up. How about including tips on how to perform admin backend search of the custom post type including meta fields/boxes? Because by default, if you even type the ID of the custom post type in the search box, it will not come back with any results.


  17. 32

    Hi Daniel,

    I know I’m a bit late, but do you know if it’s possible to configure Custom Post Types so certain post types use only a subset of your defined thumbnail sizes? If you’ve got a few different custom post types, each needing a different thumb size for it’s featured image, then the default wordpress behaviour is to create ALL thumbnail sizes, for ALL post types – which laves you with way more thumbnails than you need.

    I asked this on Stackoverflow ( a while ago, but didn’t get an answer.

    Thanks in advance, and great article!

  18. 33

    Good article. I did learn more about custom post fields from this article. It saves my time. Thanks a lot.

  19. 34

    Great step by step guide. Works perfect, well sort of.
    2 questions
    I went through the entire post and managed to add a Post-Type tilted “Sights” In the dashboard I get the Sights menu with All Sights, Add New, Sight Categories, but I don’t have the “Tags” item. How would I add that. In other words what did I miss?

    Also while I managed to get things to work and created a post however when I try to view the post on the site although I published it I seem to get an error 404 page not found message. Any thoughts?

    Thanks again for this easy to understand guide. Great work

    • 35

      Daniel Lemes

      June 12, 2013 11:03 am

      If anyone is interested, i’m using…

      register_taxonomy(‘post_tag’, ‘product’);

      …after “register_taxonomy( ‘product_category’, ‘product’, $args );” to have the tag box back.

    • 36

      I also got a page not found error when I tried to create custom post types first time. You can solve this by going to the ‘permalink setting’ page and pressing the ‘Save Changes’ button. This will reset the permalink setting in your WordPress installation. Now you should be able to view the posts of the new custom post type you created by visiting their view post(or whatever name you gave these post types) link. Let us know if this doesn’t work.

  20. 37

    Truly, truly helpful. I had hit a roadblock in just getting the custom post to display in a template, and this was exactly what I needed. Thank you for making a clear, direct set of instructions. I’m not sure why it’s so hard for so many other bloggers!

  21. 38

    Rafał Gicgier

    December 7, 2012 12:32 pm

    Yo mate,

    Nice and detailed article. I admire the amount of devotion and attention to the detail with articles you submit here.

    But please remember to use after the Custom Query :))

    This is a HIGH priority call.:)

  22. 39

    Jonathan Goldford

    December 17, 2012 7:34 pm

    Really nice article and very clearly explained. I’ve seen plenty of articles that make creating custom meta boxes seem really complex, but this lays it out very simply.

    Thanks for writing.

  23. 40

    You can also add on this article on how to add taxonomy filter

  24. 41


    Managed to get all that working but I’m having a problem with displaying the custom meta data.

    I’m using Headway, not sure if that makes a difference.

    The data is displaying thusly:

    yacht_price: 180,000

    I need that to be:

    Price: 180,00

    Any ideas why it’s doing this?



  25. 42

    This is good article.

    You can use to save development time.

  26. 43

    Christopher Tierney

    February 18, 2013 12:26 pm

    I had one snag when I try this process- when I hit ‘save’ (WordPress 3.5.1) and the page reloads the custom data is not present in the custom meta field… strange.

    I can’t determine whether or not the code in the tutorial was meant to do this, so tried an exact copy/paste of the tutorial code but I’m still getting “enter a price” rather than the text I wished to save. Am I doing something wrong?

  27. 44

    Nice article! I wanted to bookmark this article to my GooglePlus +1 tab but the only bookmark button you have is to Twitter. I recommend that you add a plug-in or code for social bookmarking. For GPlus – I highly recommend something with a counter so that it can just be added to the +1 tab without having to share as a post.

  28. 45

    Giancarlo Leonio

    March 8, 2013 6:00 pm

    Thank you for this tutorial on custom post types. I have a WordPress custom post types board on Verious with your post. I’d like to share it with you. Hope you find it useful!

  29. 46

    Kevin Partner

    March 21, 2013 11:34 am

    I don’t know whether this has changed recently, but to get “category” style hierarchical taxonomies to work properly, the line:

    register_taxonomy( ‘product_category’, ‘product’, $args );

    needs to be

    register_taxonomy( ‘product_category’, array(‘product’), $args );

    At least it does for me! I can now go to [website]/[taxonomy]/[category] and see all custom posts in that category.

  30. 47

    Thanks for the tutorial. One bug I noticed. In the code sample:


    function my_taxonomies_product() {
    $args = array();
    register_taxonomy( ‘product_category’, ‘product’ $args );

    add_action( ‘init’, ‘my_taxonomies_product’, 0 );


    there is a missing comma – the register_taxonomy function should read:


    register_taxonomy( ‘product_category’, ‘product’, $args );


  31. 48

    This post from Smashing Team enabled me to get all the information from one place/ post instead of searching for different tuts. online . I made a client website using the custom post types power and off course with your help SM.

  32. 49

    Hello, thanks for this great tutorial,…
    Can anybody show me how to display all categories of taxonomy as menu links, I’m trying to make a portfolio template and there is no any certain taxonomy term. ( Client would be able to create his/her own taxonomy term(categories). )

    Thanks again.

  33. 50

    > This is a simple box which only contains the price, so we have created a label and an input to manage it. A nonce field is also present, which adds security to the data submission.

    Where is corresponding code?

  34. 51

    Iftekhar Bhuiyan

    May 18, 2013 8:00 pm

    I am working on a Project which requires me to add Products. I was using regular blog posts (customized) to add products. This piece of code will help me for sure. Thank you.

  35. 52

    Thanks for the article, great. Just one thing… when I follow your code for saving the plugin data, the nonce gets in the way on the “create New …” page (I get a php notice, saying that the nonce name is not an index of the $_POST variable.. so I added a small line at the top of it checking for the existence of the nonce in the post variable and returning if not there, just before the autosave bit…

    if ( ! isset( $_POST[‘my_nonce_name’]) ) { return; }

    That got rid of the notice… anything wrong with that?

  36. 53

    Tabarak Siyal

    June 1, 2013 1:08 pm

    I am having some error on this line, I’ve just copy and paste your codes to see how its working but on this code i am having some sort of error, I don’t know why
    register_taxonomy( ‘product_category’, ‘product’ $args );

  37. 54


    I can see all posts from “product`s type post” at archive-products.php.

    But If i create a product category:
    its show me at archive.php file. How can i do to see post types categories at custom archive php file?


  38. 55

    Adam Patterson

    June 19, 2013 8:46 am

    The one thing that drive me nuts with custom post types was the lack of navigation support for them.

    next_post_link() and get_next_posts_link() didn’t work at all so I ended up writing a plugin that “seems” to work for me.

    Let me know if you have any success, it should give you next / previous links inside a single custom post, I don’t think It will work in custom taxonomy but let me know :)

  39. 56

    This is great!

    I’ve trying to get custom post types to work for so long!
    Would you be able to point me to some documentation of how to populate the meta box? I had a look at the article linked to but I am confused as to how that ties in with the custom post type.


  40. 57

    Thanks for all of your help, your article help me a lot.

  41. 58

    This is a great article—thanks so much!

    I have two questions:

    1. I’m having a bit of a comprehension issue which may be to do with semantics, and/or also to do with a plugin i’m using, and their terminology.

    When I have more time, i definitely want to learn how to add custom posts and taxonomies and meta boxes to my theme (i think i’ll get there, since while reading the majority of this article and other tutorials, i half-way ‘get it’, in theory at least) but for now I am using the Toolset ‘Types’ plugin.

    The plugin enables me to create custom fields and fieldsets, that can appear on my custom post types. Are these actually meta-boxes with meta-data, or are they ‘custom field boxes’ with…. custom field data? This is confusing me, because the plugin doesn’t mention meta-data or meta-boxes anywhere. (But maybe they do that so as not to confused non-coders who more readily understand the term custom field, than meta-data.

    2. This second question may well be related to the first, I’m not sure… but when is it more appropriate to use meta-boxes with meta-fields, than custom hierarchical taxonomies. For example, if i have post types that i want to be styled a certain way (i.e. have a class attached to it), i understand a custom meta-box with radio buttons (is this possible) would be a good way to have the user select the appropriate class, but could this not be done also using custom taxonomies?

    I hope i made sense :( ??

    Thanks in advance of any light you can shed!

  42. 59

    Martina Oswald

    October 10, 2013 12:01 am

    I was searching for hours to display my custom post type of each custom taxonomy and never found a working solution. 10min reading your “Displaying Your Content” chapter and I finished the jobs!

    Thank you soo much for this information. It seems that my problem was so trivial that anyone wrote about it. Anyone, but you :)

  43. 60

    Hi, Great article.
    Can you please let me know how to read custom post individually. I mean like original post in single.php
    I would be glad.

  44. 61

    I’ve been working through several different tutorials on creating custom post types and I always hit the same block………I can get it to work and view the post as a draft, but when I publish the custom post and go to view it I get a 404 for page not found.

    can anyone tell me why this is happening and how I can fix the problem.

    many thanks in advance!!!

    • 62

      Edward Beckett

      December 28, 2013 7:54 pm


      You need to read up on the Register Post Type docs on the WP Codex….

      The second argument for the register_post_type function takes an array of key value pairs for the attributes of your custom post type.

      You can set your custom post type’s url structure there.

      For example…

      ‘rewrite’ => array(
      ‘slug’ => ‘YOURCPTSLUG’,
      ‘with_front’ => BOOL, [true,false]
      etc, etc..


  45. 63

    Hi Great article!!!
    one question:
    i have one CPT with a taxonomy called ‘pagato’
    how can navigate (show the link to the previous/next article) from single post to another only if is in the same taxonomy?
    i tried to use:
    previous_post_link( ‘%link’, ‘%title’, true );
    next_post_link( ‘%link’, ‘%title’, true );

    with TRUE but it doesn’t works!!
    can anyone help me?

  46. 64


    Thank you for this post. My question is, when we create custom post type or when i use advance custom plugin, then in listing like post it shows post name, category date etc, how can i show different fields in listing of my custom post type!

    Thank you

  47. 65

    nice tut!

    i have a problem with paging in taxonomy. i used pagenavi plugin, when i redirect to /page/2/ , website redirect to 404 page. can you help me ?

  48. 66

    Fantastically detailed write up. I tried but gave up editing my functions.php files to create custom post types, mainly because adding all the custom fields I wanted was a bit of a nightmare.

    I recommend anyone who isn’t great at coding or just wants to make their lives a bit easier should check out the Types & Views Plugins. I use them all the time and wrote an article about them if anyone is interested. Cheers, Neil

    Article link:

  49. 67

    Great article, is there a way to call a custom post type via a metabox from another custom post type and display that information? Say you had a movies CPT and an actors CPT, in your movies CPT there is a select metabox with all the actors CPT listed. I would like to choose an actor and publish the movies CPT with both information displayed on one page. Possible?

  50. 68

    HP SuperKing

    March 12, 2014 9:16 am

    You rock men. Custom post_type for my product

  51. 69

    Thanks for the article, great help.

    However, as is I was having issues editing custom post type posts. Any time you edit, the custom meta-content boxes are empty. Then on submission all values are cleared (assigned the empty values).

    We can clear this up by loading any existing values using get_post_meta().

    Add this to the last line of the ‘product_price_box_content’ function:
    … placeholder=”enter a price” value=”‘ . (get_post_meta($post->ID, ‘part_id’, true) ?:”) . ‘”/>’;

    This way you’ll get any existing values loaded into the content meta-boxes on edit.

    (Sorry to write it that way but if I place the entire line of code it triggers the spam bot.)

  52. 70

    Bobby Adamson

    March 17, 2014 1:57 pm

    Developers using this code will have an issue with custom meta boxes not saving content on submit or update. In fact, the content is being saved, but the custom meta box is not displaying the information that is saved to it. The issue there is that if a user goes in to the post and edits it without re-entering the custom meta content; the box will stay empty and save a null value over the previously entered content. This requires the user to re-enter the same content each time they edit the post.

    The solution is similar to what Tim wrote above me, but written out a little more to understand what’s going on easier (also Tim’s solution did not work for me.) First, in the “product_price_box_content” function, create a $meta_value variable that stores the meta data like so: $meta_values = get_post_meta($post->ID, ‘product_price’, true);

    Then after the label for the product price, write an if statement. If ($meta_values != ”) { echo the input with value=”‘ . $meta_values . ‘” (or whatever you’ve chosen to name your meta value variable) }

    else { echo the input with no value }

    This conditional asks “Is $meta_values a non-null value?” meaning has there been content saved to this meta box before? If yes, then echo the input box with the content that has been entered before as the default value of the box. This verifies that your content has been saved, and makes it unnecessary to re-enter the same content each time you edit your post.

    If the answer is no – meaning either this is the creation of the current post, or the post is being edited and a value was not entered for this meta box before, then echo the input with no default value.

    If anyone on the smashing magazine team is reading this I would highly recommend either adding this solution to the blog post or addressing the problem in some kind of way. I am using this to write some custom post types and know numerous people who have used this post before. Thank you for this fantastic article and I hope my solution can help in some way.

    Here is a pastebin of my solution

    • 71

      Hey Bobby,

      Thanks for this answer. I was pulling my hair out wondering why the custom meta box was not saving my input. Again, many thanks and I really appreciate it.


    • 73

      Thank you thank you thank you Bobby! I have been going nuts over this and i’ve done a couple of other tutorials about custom meta boxes and have come across this problem. It seems simple for someone that is knowledgeable on it already but for people just learning it is a massive brick wall to run into. This is why having comments on web dev blogs is helpful

    • 75

      Bobby Adamson

      March 10, 2015 7:43 pm

      I have received a few messages thanking me for this solution. I was more than happy to help. For the sake of keeping this result findable, I have uploaded my solution to Gist as well here:

  53. 76

    Thanks for the tutorial. Do you know a godo way to get the urls to be like

    instead of


    it sounds like a simple thing to do but notihing seems to work without conflicting with something else.

  54. 77

    Aleksandar Misin

    April 11, 2014 12:37 pm

    Thank you very much!!
    I did searched almost the entire universe to find the answer. And answer was I did only need to add custom taxonomy :)

    Thanks very much!

  55. 78

    Hi Daniel,

    Just like to say thanks for your article. I found it very useful indeed, and it has helped me gain a better understanding of how WordPress functions from the inside.



  56. 79

    Thanks for this post, very very good
    I need show multiple post type in home page
    for example i create post type for team, product, price table and… so show all post in this post type (team, product, price table…) in home page
    Please help…

  57. 80

    I have been having issues with the WP_QUERY not return the correct results. I found that it was a problem with the underscore in the taxonomy. So i changed this:

    register_taxonomy( ‘product_category’, ‘product’, $args );

    to this:

    register_taxonomy( ‘productcategory’, ‘product’, $args );

    and everything is now working nicely.

    Just thought i would share, just incase someone else has the same problem.


  58. 81

    Does anyone know how to add a duplicate button to the custom post type?

  59. 82

    Great article.

    I’ve just got back into WordPress after hand coding all the projects I’ve worked on for the last few years.

    This is exactly what I needed to figure out as I’ve got a client that need to separate there venues and supply location / contact / description / images ……

    My question now is – Is it possible to make a search framework that will tie into all these extra meta data?

    For example search for a venue by region / county / town / activities

  60. 83


    Please let me know if it’s posible to export the custom type with all the custom fileds that i will creat ?

    I need for example to create one type post with the name “travel listing” with more custom fields: name, price, transport, etc…

    Then i want to export into a .xml file the listing from “travel listing” type post?
    The format of the must be something like that:

    Listing type
    – name of the listing
    – Price
    – include price
    – ect….

    Please let me know uf that it’s posible?

    many regards

  61. 84

    Awesome tutorial. Thank you very much.

  62. 85

    nice article , really helped me make a post for affiliate products. Can I get the code to display the affiliate link in the meta boxes

  63. 86

    thank you very much for this nice tutorial. it helped me alot. i just found a little mistake: it must be “get_the_ID()” instead of “get_post_ID()” in the “Displaying Metadata” section.

  64. 88

    Fantastic post. It took me a little while to see the value in using custom post types but once I got my head around why they are useful I can’t stop using them. Every site I now work on I use custom post types, they make orgainsing different content so much easier and when combined with custom fields they make creating content simpler too.

    I’ve just recorded a video showing how I use custom post types on one of my websites, which some people new to the subject may find useful.

    Cheers, Neil
    Link to the video:

  65. 89

    very useful tutorial. thanks dear. I am new to wp.
    I have create the texanomy area and add different categories/texonomies in that but now I have tried different ways that how to fetch the custom post data based on that particular texonomy.

    here is my code

    $query = new WP_Query( array(‘post_type’ => ‘books-shelves’, ‘books_category’ => 7, ‘posts_per_page’ => 8) );
    while ( $query->have_posts() ) : $query->the_post();

    will anybody please tell me the correct syntax. thanks in advance.

  66. 90

    Thank you Daniel, this tutorial really helped me!

  67. 91

    Hey Daniel,

    Thanks for the post. I came back to it a number of times to get things right. It helped me creating a humble plugin that uses this setup and has a decent user base (no link, not here to drive clicks :).
    Having said that, once this plugin is active, I noticed everytime I create a regular post, my database gets cluttered with additional metaboxes (with no value).

    Where should I look at to limit the scope of my metaboxes to my custom post type ?

    Keep up the good work !

  68. 92

    Hello Sir, Thank you for your detailed tutorial. But I encountered an error after I added this function below:

    function my_taxonomies_product() {
    $labels = array(
    ‘name’ => _x( ‘Product Categories’, ‘taxonomy general name’ ),
    ‘singular_name’ => _x( ‘Product Category’, ‘taxonomy singular name’ ),
    ‘search_items’ => __( ‘Search Product Categories’ ),
    ‘all_items’ => __( ‘All Product Categories’ ),
    ‘parent_item’ => __( ‘Parent Product Category’ ),
    ‘parent_item_colon’ => __( ‘Parent Product Category:’ ),
    ‘edit_item’ => __( ‘Edit Product Category’ ),
    ‘update_item’ => __( ‘Update Product Category’ ),
    ‘add_new_item’ => __( ‘Add New Product Category’ ),
    ‘new_item_name’ => __( ‘New Product Category’ ),
    ‘menu_name’ => __( ‘Product Categories’ ),
    $args = array(
    ‘labels’ => $labels,
    ‘hierarchical’ => true,
    register_taxonomy( ‘product_category’, ‘product’, $args );
    add_action( ‘init’, ‘my_taxonomies_product’, 0 );

    And I try to submit new category I the error are occurred below.

    Fatal error: Call to undefined function auth_redirect() in D:\xampp\htdocs\wp\dev_plugins\basic_plugins\wp-admin\admin.php on line 80

    Which I could not continue to save the category. Please advise. Thank you.

  69. 93

    Ashish Ajani

    March 13, 2015 8:17 am

    Nice article Daniel. I would like to share a free and useful WordPress plugin “WP Show Stats” here. This plugin can show how many inbuilt and how many custom post types used in WordPress site. It will be very helpful for admin to keep track on how custom post types used and how many records are the for each custom post types. It also provides other various statics about usage of blog posts, categories, tags, users, comments, etc..
    I hope it will be useful for other readers of this post as well.

  70. 94

    Thanx sir this code is very useful for me :) Could you also please write the code how to add multiple images of post and multiple meta box? Thanks in advance.

  71. 95

    Thanks for the great really helps a noob like me!

    Everything works great except for the archive page, it comes up with no results. So I setup an archive-[post_type].php with code provided above but in vain…
    Could anyone guide me to where I could look to get this working? thanks in advance…

    …and if you are feeling generous please also maybe point me to somewhere I can learn how to build a custom search form to include the custom meta box data and list the results?

  72. 96

    Cees Rijken

    July 2, 2015 12:56 pm

    I have to say: this was an awesome piece, which made my life a lot easier when building my own F.A.Q. for WP! Cheers!


↑ Back to top