Custom post types in WordPress

One of the things I’ve been hoping would be implemented in WordPress for years is the ability to create custom content types. The inability to do this has been WordPress’ weakest point when compared to rivals in the CMS market.

In WordPress 3.0, we’ll have the capability to easily create and manage content via custom post types. With a few lines of code in a plugin, you can have your own types.

There are other 3.0 features such as menu management and multi-site, but this is the most promising feature coming. In this tutorial, I’ll walk you through the creation of custom post types and how they can be used for your blog.

Note that I'll use "post" and "posts" a lot to refer to custom post types in this tutorial. I'll try to separate this from what you likely know as a post by calling it a "blog post."

What are custom post types?

Don’t be confused by the term “post” in the name. It is actually an extremely generic term and should not be considered the same thing as a blog post. If you prefer, you can replace it with “content” instead.

Custom post types don’t have any strict rules that define what they are. They can represent any type of content you want. For example, WordPress ships with several default post types.

  • Blog Posts
  • Pages
  • Attachments
  • Revisions
  • Nav Menus (WordPress 3.0)

You should think of them as a way to create, edit, and store information in the same way as blog posts but with much more creative control.

One thing I would warn against is trying to separate your blog posts in this way. You have categories, tags, and custom taxonomies at your disposal if you’re just looking for a way to label them.

Ideas for custom post types

While WordPress ships with some good default post types that are great for most sites, some of us need alternatives. I’ll list some things below that I think would be useful and link to examples if I can.

Don’t let your imagination be limited by my list of examples. Also, look for some information on the forum and ticket system ideas from me in the future. Those two are projects I’ve been building and would love to get some feedback on.

Custom post types in the admin

Once you create a custom post type, WordPress can handle all of the admin stuff for you. By default, the administration interface will be the same as what you get with blog posts and pages. Of course, you’re welcome to get more advanced with it and change it into whatever you like.

In this tutorial, I’m creating a post type called Super Duper. With the minimum post type settings, the admin will look like the screenshot below (click image for more detailed view):

Add new post with a custom post type

Creating custom post types (basics)

WordPress 2.9 introduced the register_post_type() function. It didn’t do a lot at the time, but WordPress 3.0 will make this a powerful tool.

To get the basic functionality of a custom post type working, we hardly have any code to input. Before you start, you at least need a few things: a name, two labels, and whether your post type will be public (you’ll likely want this).

When creating custom post types, you need to add your code to a plugin file. While it’s possible to do this via the functions.php file in a theme, it is incorrect to do so.

Let’s set up our Super Duper post type.

add_action( 'init', 'create_my_post_types' );

function create_my_post_types() {
	register_post_type( 'super_duper',
			'labels' => array(
				'name' => __( 'Super Dupers' ),
				'singular_name' => __( 'Super Duper' )
			'public' => true,

You’ll notice that there’s not a lot going on there. We added two parameters: a post type name and an array of arguments. All of the possible arguments are detailed in the next section.

If all you’ll ever want to do is something as basic as having a title field and content textarea, you can skip over the next portion of this tutorial.

Creating custom post types (in detail)

In the basic section above, we used two arguments to create our custom post type. However, the register_post_type() function has well over 20 arguments you can use. It gives you a lot of control over the specifics of your post type without a lot of hassle. You can mix and match these in all sorts of ways that I can’t even come close to covering in this tutorial.

Below, I’ll describe each of the arguments available for use. Each section will have an example of how to add the argument to the arguments array.


The labels argument is an array of strings that represents your post type in the admin. Each string is a bit of text shown for particular function in the admin. By default, non-hierarchical post types will have text with “post” in them and hierarchical post types will have text with “page” in them.

It is particularly important that you make these strings translatable if you’re creating a plugin for public use.

This list is a set of general examples of when each string is used. However, each may be used in multiple places in the admin.

  • name: The plural form of the name of your post type.
  • singular_name: The singular form of the name of your post type.
  • add_new: The menu item for adding a new post.
  • add_new_item: The header shown when creating a new post.
  • edit: The menu item for editing posts.
  • edit_item: The header shown when editing a post.
  • new_item: Shown in the favorites menu in the admin header.
  • view: Used as text in a link to view the post.
  • view_item: Shown alongside the permalink on the edit post screen.
  • search_items: Button text for the search box on the edit posts screen.
  • not_found: Text to display when no posts are found through search in the admin.
  • not_found_in_trash: Text to display when no posts are in the trash.
  • parent: Used as a label for a parent post on the edit posts screen. Only useful for hierarchical post types.
'labels' => array(
	'name' => __( 'Super Dupers' ),
	'singular_name' => __( 'Super Duper' ),
	'add_new' => __( 'Add New' ),
	'add_new_item' => __( 'Add New Super Duper' ),
	'edit' => __( 'Edit' ),
	'edit_item' => __( 'Edit Super Duper' ),
	'new_item' => __( 'New Super Duper' ),
	'view' => __( 'View Super Duper' ),
	'view_item' => __( 'View Super Duper' ),
	'search_items' => __( 'Search Super Dupers' ),
	'not_found' => __( 'No super dupers found' ),
	'not_found_in_trash' => __( 'No super dupers found in Trash' ),
	'parent' => __( 'Parent Super Duper' ),


The description argument allows you to write a text string of your post type. Thus far, I haven’t seen this used anywhere in the WordPress admin, but I’m sure custom post type plugins could take advantage of this.

'description' => __( 'A super duper is a type of content that is the most wonderful content in the world. There are no alternatives that match how insanely creative and beautiful it is.' ),


The public argument is a kind of catchall argument for several other arguments and defaults to false. Depending on whether it’s set to true or false, it’ll automatically decide what other arguments should be unless they’re specifically defined. If you’re looking for finer control over the public arguments, there are three specific arguments you may set:

  • show_ui: Whether to show the administration screens.
  • publicly_queryable: Whether queries for this post type can be performed from the front end.
  • exclude_from_search: Whether the posts should appear in search results.
'public' => true,
'show_ui' => true,
'publicly_queryable' => true,
'exclude_from_search' => false,


By default, a new post type is added after the Comments menu item in the admin. But, you have to ability to move it to a position more suitable for you. Default WordPress menu items are set apart by integrals of 5. For example, using 20 will add your menu item after Pages.

'menu_position' => 20,


New post types will default to the Posts menu icon, but if you want to mix it up a bit or give your post type some separation from other elements, you can define a custom icon. You only have to input a a custom URL to an image file.

'menu_icon' => get_stylesheet_directory_uri() . '/images/super-duper.png',


The hierarchical argument allows you to choose whether you want your post type to be hierarchical. It defaults to false. If you set it to true, your posts will behave like pages in WordPress.

'hierarchical' => true,


The query_var argument allows you to control the query variable used to get posts of this type. For example, you could use it with the query_posts() function or WP_Query class. This will default to the name of your taxonomy.

'query_var' => true,

capability_type / capabilities

The capability_type argument is another catchall argument for several more specific arguments and defaults to post. It allows you to define a custom set of capabilities, which are permissions to edit, create, and read your custom post type. If you’re unfamiliar with capabilities, I highly recommend reading my guide on users, roles, and capabilities.

If you just want to keep the same permissions you have with blog posts, leave this at the default. Otherwise, you can either set capability_type to something custom or define each specific capability in the capabilities array.

  • edit_post: Whether someone can create and edit a specific post of this post type.
  • edit_posts: Capability that allows editing posts of this post type.
  • edit_others_posts: Capability that allows editing of others posts.
  • publish_posts: Capability to grant publishing of these types of posts.
  • read_post: Capability that controls reading of a specific post of this post type.
  • read_private_posts: Capability to allow reading of private posts.
  • delete_post: Capability that grants the privelege of deleting posts.

For most people that need control over these things, it’s easier to just change capability_type to something like super_duper. WordPress will automatically switch the other caps to follow this pattern. For example, the edit_post capability would become edit_super_duper.

Of course, capability control means nothing without being able to choose who has the capabilities. That’s what my Members plugin was created for. It’s ideal for this scenario.

/* Global control over capabilities. */
'capability_type' => 'super_duper',

/* Specific control over capabilities. */
'capabilities' => array(
	'edit_post' => 'edit_super_duper',
	'edit_posts' => 'edit_super_dupers',
	'edit_others_posts' => 'edit_others_super_dupers',
	'publish_posts' => 'publish_super_dupers',
	'read_post' => 'read_super_duper',
	'read_private_posts' => 'read_private_super_dupers',
	'delete_post' => 'delete_super_duper',


The supports argument allows you to define what meta boxes and other fields will appear on the screen when editing or creating a new post. This defaults to title and editor. There are several available options:

  • title: Text input field to create a post title.
  • editor: Content input box for writing.
  • comments: Ability to turn comments on/off.
  • trackbacks: Ability to turn trackbacks and pingbacks on/off.
  • revisions: Allows revisions to be made of your post.
  • author: Displays a select box for changing the post author.
  • excerpt: A textarea for writing a custom excerpt.
  • thumbnail: The thumbnail (featured image in 3.0) uploading box.
  • custom-fields: Custom fields input area.
  • page-attributes: The attributes box shown for pages. This is important for hierarchical post types, so you can select the parent post.
'supports' => array( 'title', 'editor', 'excerpt', 'custom-fields', 'thumbnail' ),


The rewrite argument allows you to define the permalink structure of your posts when viewing the single post. For example, you may want to have a structure like WordPress will set up a default structure based on your taxonomy name. The rewrite argument can be set to true, false, or an array of values. It takes two arguments:

  • slug: The slug you'd like to prefix your posts with.
  • with_front: Whether your post type should use the front base from your permalink settings (for example, if you prefixed your structure with /blog or /archives).
'rewrite' => array( 'slug' => 'cool', 'with_front' => false ),


If you have some preexisting taxonomies, you can allow posts of this type to also use those taxonomies. You just have to set an array of taxonomy names that you’d like for it to use. WordPress will handle all the administration features for you.

Note that I’ll be covering the integration of post types and taxonomies in more depth in a future tutorial.

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


You can use the can_export argument to decide whether posts of your post type can be exportable via the WordPress export tool. By default, this is set to true.

'can_export' => true,


This feature will likely only be useful to developers. You can create a custom callback function that is called when the meta boxes for the post form are set up.

'register_meta_box_cb' => 'your_callback_function_name',


I won’t pretend to know much about permalink endpoint masks. I just know you can define a custom one for your post type. Most of you won’t need this. For the purposes of this tutorial, I’ll leave this at the default.

'permalink_epmask' => EP_PERMALINK,

Viewing a custom post type

WordPress will show the singular view of your custom post types without any extra work. Once you create a new post, you’ll be able to view it by clicking on the View Post link.

View post link in the WordPress admin

If your WordPress theme has a single.php template, this will be used to display your custom post type. If not, it will fall back to the index.php template. Since this is a “custom” post type, I’ll assume you want to customize how it’s output. To overwrite the default, you can create a custom template. Our template will be called single-super_duper.php.

You can go as crazy as you like with this template. All of the standard WordPress template tags will work just fine here as well. I could give you endless pages of examples of how to customize this, but I won’t. I’d rather allow you to get creative on your own.

If you're using a smart theme like Hybrid, this template will be super_duper.php. Hybrid has long supported custom post types and uses a different template hierarchy, which is detailed in its tutorials. WordPress 3.0 will be a fun time to use this theme because of the ultimate control it gives you over templates like this.

Showing multiple posts from our custom post type

Let’s suppose I wanted to show the 10 latest “super dupers” I’ve published. We’ll use the standard WordPress loop for this and customize it however we like. Here’s a basic example:

<?php $loop = new WP_Query( array( 'post_type' => 'super_duper', 'posts_per_page' => 10 ) ); ?>

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

	<?php the_title( '## <a href="' . get_permalink() . '" title="' . the_title_attribute( 'echo=0' ) . '" rel="bookmark">', '</a>' ); ?>

	<div class="entry-content">
		<?php the_content(); ?>
<?php endwhile; ?>

Obviously, this is an extremely simple example. Understanding what you can do with the loop is outside the scope of this tutorial. The most important part is giving the post_type argument the name of our post type.

Getting the post type of a post

An old, yet useful, function in WordPress is get_post_type(). It allows you to check the current (if no post object or ID input) or a specific post’s post type. It will return the name of the post type for the post as a string.

$post_type = get_post_type( $post_id );

Checking if a post type exists

WordPress 3.0 will introduce a conditional tag called is_post_type() whose purpose is twofold. Its first purpose is to check whether a post type or types exist. In order to use it, you can input a custom post type name or array of names into the function call.

if ( is_post_type( 'super-duper' ) )
	echo 'Super Dupers are working!';
	echo 'Super Dupers are not working!';

Checking if a post is of specified post type

The second purpose of the is_post_type() function allows you to check a specific post against a specific post type(s). Let’s assume we want to check if one of our posts has the super_duper post type.

if ( is_post_type( 'super_duper', $post_id ) )
	echo 'This is a not a blog post.  It is a super duper!';
	echo 'This is not a super duper. [insert sad face]';

Checking if a post type is hierarchical

You may want to treat hierarchical post types differently than other post types. To figure out if a post type is hierarchical, you can use the is_post_type_hierarchical() conditional tag. It will take in a post type name, post ID, or post object as its single parameter.

For example, let’s check if our super duper post type is hierarchical:

if ( is_post_type_hierarchical( 'super_duper' ) )
	'Super Dupers should not be hierarchical!';
	'Super Dupers are definitely not hierarchical! We stick it to the man!';

Getting a post type object

In some scenarios, you may want to get the arguments (all those things we went over earlier) for a specific post type. It is extremely important for plugin/theme authors to respect these arguments when creating plugins/themes that interact with custom post types.

Let’s suppose we want to call a specific function if the super duper post type has set show_ui to true.

$post_type = get_post_type_object( 'super_duper' );

if ( $post_type->show_ui )

Adding and removing support for post type features

The add_post_type_supprt() function is no different than using the supports argument when first creating your post type. This is likely more useful if you’re using a plugin/theme that has defined a custom post type.

So, let’s suppose you’re using a plugin that creates a new post type for you. Your post type doesn’t have support for thumbnails, but you think this feature would work well on your site.

add_post_type_support( 'post_type', 'thumbnail' );

The opposite of this function is remove_post_type_support(). So, let’s further suppose that your plugin author added support for excerpts, but you don’t want to use this feature.

remove_post_type_support( 'post_type', 'excerpt' );

Checking if a post type supports a specific feature

As a plugin/theme author, this will likely be useful. You wouldn’t want to try and display stuff that doesn’t exist for a custom post type. For example, your theme may wrap post content with a

or something of that sort. This is fairly common. But, you may want to remove it if the post type doesn’t have written content.

<?php if ( post_type_supports( 'super_duper', 'editor' ) ) { ?>

	<div id="post-<?php the_ID(); ?>" <?post_class(); ?>>
		<?php the_content(); ?>

<?php } ?>

Be careful with this though. A post type may have other means of defining something like the content, and this should only be taken as an example of how to use the post_type_supports() function.

Getting registered post types for use in PHP

As a plugin/theme developer, you may need to get all the registered post types for use in a function of your plugin/theme. You’d use the get_post_types() function for this.

In the below example, I’ll output a list of post types by singular label that are shown in search results.

<?php $types = get_post_types( array( 'exclude_from_search' => false ), 'objects' ); ?>

	<?php foreach ( $types as $type )
		echo '<li>' . $type->singular_label . '</li>';

Help! I’m getting a 404!

If you’re getting a 404 message when trying to view a post, don’t worry. This is supposed to happen when setting up new post types. The easiest way to fix this is to simply visit your Settings > Permalinks page. You don’t have to save your permalinks but just visit the page. This will flush your rewrite rules.

If you’re a plugin author, you can save yourself some support questions by flushing the rewrite rules on activation of your plugin.

Is this a theme or plugin feature?

I’ve had a hard time trying to decide which to call it, and have ultimately decided that it’s both. It’s a marriage of the two because both need to be in sync to fully enjoy the benefits of custom post types.

More than likely, you’ll see post types registerd from within a plugin. But, they have to be displayed by your theme. If you’re creating this all yourself, you can simply drop it all in your theme.

If you’re a theme author, you should be aware that plugins may add new post types. Do yourself and your users a favor by not assuming too much. Keep your code flexible enough to give a default view for custom post types and easy enough to handle when creating custom templates for those post types.

The tip of the iceberg

I haven’t even scratched the surface of what’s possible with custom post types. Even the new navigation menus feature of WordPress 3.0 will be built with them. While I know some people in the community don’t agree with this decision, I wanted to point it out because it’s a unique example of what’s possible.

Of my list of about 1,000 things I wanted to share in this tutorial, I might have covered about 20 of them. Given enough free time, I could write a book on using custom post types. Some stuff should be left open to exploration though, and I encourage you to try everything I’ve written about and move beyond this tutorial to creating something unique for your site.