Paulund
2013-04-15 #wordpress

Creating A Custom Post Type In WordPress

WordPress has the ability to have different types of post types, the main post type you will use in WordPress is the post type called post. But there are other defaults such as Pages and Attachments. All the post types in WordPress are stored in the same table wp_posts there is a column on this table which will decide the type of post, this is defined as post_type. In version 3.0 of WordPress a function was created to allow you to easily create your own post types.

Default Post Types

The 2 main post types are the post and the page, the main differences between these two post types is how they are used in WordPress to display your content. A post is mainly used for a blog like structure, is will allow you to easily display all your content is a published date order. This means that you can display a list of all your posts in the order of when they were published. You can use this post type for any content that can be ordered by date, it doesn't have to be blog posts. Another difference of posts over pages is the ability to assign categories and tags to group the posts together, for example in a blog you can create an article category, tutorial category, a news category. Now you can view all the posts in these categories again in a date order, to show all the possible posts. Pages are not date specific nor can they be assigned to categories or tags. For this reason they will normally be used for static type pages such as a contact form, a displaying category pages, a sitemap, terms and conditions etc. Pages can be assigned page templates, so the you can create a PHP template of how the content will be displayed, then all you have to do is assign this template to the pages for them to be different than the standard blog post pages.

How To Create A Custom Post Type

Custom post types was the first step from changing WordPress from a blog application into a full CMS. If you have to create a new type of content then you have to make a decision on how this content is going to be used on your application, does the content fit into your standard blog posts and therefore can be different by assigning a different category to it? Is the content going to fit more into a page and isn't date specific? Or is this a new type of content such as if you were creating a shop and want to display products then this doesn't fit as a post or a page so we need to create our own custom post type. Creating a new custom post type will add a new item on the admin menu for this new post type so that you can easily create new content. To define a new custom post type you need to use the function register_post_type(). This function takes 2 parameters the first being the new name for the post type and the second being a list of arguments to define the behaviour for the post type.


add_action( 'init', 'create_post_type' );
function create_post_type() {
        $args = array();
	register_post_type( 'post_type_name', $args);
}

There is a list of arguments that you send to this function and each will define how the post type can be used. - label - A string for the plural descriptive name for the post type.

  • labels - An array of descriptive names for the post types.
  • description - A short description of the post type.
  • public - A boolean value on if the post type can be seen by the public and therefore can be viewed from the front-end.
  • exclude_from_search - A boolean value on if this post type can be searched.
  • publicly_queryable - A boolean value on if this post type can be queried.
  • show_ui - A boolean value on if Wordpress will generate a UI to manage the post type.
  • show_in_nav_menus - A boolean value to decide if you can display this post type on menus.
  • show_in_menu - A boolean value to decide if this post type is displayed in the admin menu.
  • show_in_admin_bar - A boolean value to decide if the post type is displayed in the admin bar.
  • menu_position - An integer to position the post type menu.
  • menu_icon - A URL to display an icon next to the menu item.
  • capability_type - The string to use to build the read, edit, and delete capabilities.
  • capabilities - An array of the capabilities for this post type.
  • map_meta_cap - A boolean value to decide if you want to use the default meta handling.
  • hierarchical - A boolean value to decide if this post type can be assigned to categories/tags.
  • supports - A array of the post features this post type supports.
  • register_meta_box_cb - To provide a callback function to create any post meta boxes.
  • taxonomies - An array of registered taxonomies that this post type can use.
  • has_archive - A boolean value to enable post types.
  • rewrite - An array of how the URL slugs will be rewritten.
  • query_var - A string to set the key for the post type.
  • can_export - A boolean value to check if this post can be exported.

Here a is example of using these arguments to create your custom post type.

add_action( 'init', 'register_cpt_cp_name' );
 
function register_cpt_cp_name() {
 
    $labels = array( 
  	'name'               => __( 'plural post type name', 'text_domain' ),
		'singular_name'      => __( 'single post type name', 'text_domain' ),
		'add_new'            => _x( 'Add New single post type name', '${4:Name}', 'text_domain' ),
		'add_new_item'       => __( 'Add New single post type name', 'text_domain}' ),
		'edit_item'          => __( 'Edit single post type name', 'text_domain' ),
		'new_item'           => __( 'New single post type name', 'text_domain' ),
		'view_item'          => __( 'View single post type name', 'text_domain' ),
		'search_items'       => __( 'Search plural post type name', 'text_domain' ),
		'not_found'          => __( 'No plural post type name found', 'text_domain' ),
		'not_found_in_trash' => __( 'No plural post type name found in Trash', 'text_domain' ),
		'parent_item_colon'  => __( 'Parent single post type name:', 'text_domain' ),
		'menu_name'          => __( 'plural post type name found', 'text_domain' ),
    );
 
    $args = array( 
		'labels'              => $labels,
		'hierarchical'        => true,
		'description'         => 'description',
		'taxonomies'          => array( 'category' ),
		'public'              => true,
		'show_ui'             => true,
		'show_in_menu'        => true,
		'menu_position'       => 5,
		//'menu_icon'         => '',
		'show_in_nav_menus'   => true,
		'publicly_queryable'  => true,
		'exclude_from_search' => false,
		'has_archive'         => true,
		'query_var'           => true,
		'can_export'          => true,
		'rewrite'             => true,
		'capability_type'     => 'post', 
		'supports'            => array( 
									'title', 'editor', 'author', 'thumbnail', 
									'custom-fields', 'trackbacks', 'comments', 
									'revisions', 'page-attributes', 'post-formats'
								),
    );
 
    register_post_type( 'cp_name', $args );
}

Function To Create Custom Post Type Labels

If you are creating a lot of different custom post types in your WordPress site, here is a handy function that will create the labels for you.

function create_post_type_labels($singular, $plural = null) {

	if ($plural === null) {
		$plural = $singular.'s';
	}

	$labels = array(
		'name'               => __( $plural, 'text-domain'),
		'singular_name'      => __( $singular, 'text-domain'),
		'menu_name'          => __( $plural, 'text-domain'),
		'name_admin_bar'     => __( $singular, 'text-domain'),
		'add_new'            => __( 'Add New '.$singular, 'text-domain'),
		'add_new_item'       => __( 'Add New '.$singular, 'text-domain'),
		'new_item'           => __( 'New '.$singular, 'text-domain'),
		'edit_item'          => __( 'Edit '.$singular, 'text-domain'),
		'view_item'          => __( 'View '.$singular, 'text-domain'),
		'all_items'          => __( 'All '.$plural, 'text-domain'),
		'search_items'       => __( 'Search '.$plural, 'text-domain'),
		'parent_item_colon'  => __( 'Parent '.$plural.':', 'text-domain'),
		'not_found'          => __( 'No '.$plural.' found.', 'text-domain'),
		'not_found_in_trash' => __( 'No '.$plural.' found in Trash.', 'text-domain')
	);

	return $labels;
}

Theme Template Files

When you create a new custom post type by default it will use the same HTML as in the single.php file, but if you created a custom post type then most likely you want to display this differently to the standard post types. Therefore you can add a new template file that will be used when your custom post type is displayed. For single templates all you have to do is create a new theme file and call is single-{posttype}.php, so for the above example you will create a new file called single-cp_name.php, this will now be used to display your custom post type. You can even display achieves for this post type by creating a new template file called achieve-cp_name.php.

Querying The Custom Post Type

If you want this new custom post type to be display with the standard post types you can change the main Wordpress query to include your custom post type. For this you need to use the pre_get_posts filter to add the custom post type to the query.

add_action( 'pre_get_posts', 'add_custom_post_type_to_query' );

function add_custom_post_type_to_query( $query ) {
	if ( is_home() && $query->is_main_query() )
		$query->set( 'post_type', array( 'post', 'page', 'new-custom-post-type' ) );
	return $query;
}

Remove Default WordPress Permalink Structure

If you have defined a permalink structure of /blog/post-name then any new custom post types that you define will inherit this permalink structure and will be prefixed with blog and the URL will look like this /blog/products/custom-post-type. This obviously isn't going to work for a section for your products, you need to be able to remove the blog prefix so you are left with simply /products/custom-post-type. To do this you need to extend the rewrite option when registering your new post type. Using the rewrite option you can define the URL that you want to use for your products section. There are two options that you need to define on the rewrite section, this is with_front and slug. Using the with_front option and setting it to false you will remove any prefix that is defined from the permalink structure, if this is set to true then it will continue using the rules defined by the permalink page. The slug is used to customise the permalink structure that you want to use on this custom post type. Use the following option on the rewrite to remove the blog prefix and define the slug as products.


'rewrite' => array(
    'with_front' => false,
    'slug'       => 'products'
)

Now when you visit your custom post types the URLs will now be http://example.com/products/custom-post-type.

Rewrite Problems

I have had this on a number of different occasions, you create a post type and then when you try to give the content on the front-end you get a 404 error and can't work out why. The post type is setup correctly, the template file is there yet nothing is displayed and you get the 404 page. Most likely the problem is that the rewrite rules have not been refreshed. The WordPress rewrite rules are stored in the wp_options table, are used to direct WordPress to the right content. Sometimes these will need to be refreshed when you create a new custom post type, there are three ways of doing this. First you can navigate to the permalinks page Settings -> Permalinks and change the permalink click the save button, then change it back to the way it was. This will refresh all the rewrite rules on your website and your custom post types should be displayed. Secondly you can open phpMyAdmin, navigate to the wp_options table and delete the rewrite rules record from this table. Next time WordPress loads it will check for the rewrite rules in this table if they aren't there then it will regenerate the rules. The third option is to place the function flush_rewrite_rules() under the register post type function. This will completely refresh the post type rules and fix any problems with redirecting.


add_action( 'init', 'create_post_type' );
function create_post_type() {
    register_post_type( 'post_type_name', $args);
}

function flush_rewrite_rules() {
    create_post_type();

    flush_rewrite_rules();
}
register_activation_hook( __FILE__, 'flush_rewrite_rules' );

Plugin Or Functions.php

Now on to the big debate when it comes to custom post types. Everyone agrees custom post types are great functionality and means you can massively expand the scope of your Wordpress application. But the problem with this is that you have to decide where you place the code to create the custom post type. In all functionality on your Wordpress site you can define it in two places, a plugin or in the theme functions.php. Each piece of functionality you add to your site should be defined to be positioned in one of these places. The way you decide on where the functionality lives is quite simple, should this functionality exist on any theme I use? If the answer is yes then add it with a plugin. If the functionality you are adding is to aid the design of the site then it should live in the functions.php file. Simple isn't it...you will find that most of the time the functionality you are adding lives in a plugin and most of the time I find that my functions.php file is almost empty. But with custom post types the lines between plugin and theme can be blurred. On one side you say this is data for the site if I change my theme then I want to be able to use this content on the next theme, so I place custom post types in a plugin. Meaning when I change my theme all the content is still going to be available I just need to add a new theme files to display this content how I want. The other side of the debate is that custom post types have data that will be displayed using the theme, you need to create custom theme files to display this data therefore custom post types should live in the theme functions.php file. The problem comes when you do want to change themes, you lose all this content...very annoying. You can still access this content in the database or by re-adding the register post type code in the functions.php file, but normal users won't know this, they will only assume that all their content is lost. On premium themes from popular marketplaces you will see lots of themes that have custom post types in the functions.php file, the most common custom post type is a portfolio section. I would be very annoyed if I spent $50 on a theme, entered all the content for my portfolio only to lose it when I changed theme. The only time I can think of when it's ok to have custom post types in the functions.php file is if you are creating theme that is extremely niche and will not be used by another site. Therefore if the user was to change themes in the future then they will have to create a custom theme for there website. Personally I think custom post types should always live in a plugin, this is data you always want to have on your site even if you change themes so it must in a plugin. The data might not be available to be seen by the theme but at least the user can see it in the admin area and knows the data is not lost. What do you think? Custom post types in a plugin or the functions.php file?

Further Reading

To read more about placing post types in plugins Justin Tadlock wrote a great articles explaining why custom post types should always be inside a plugin. Why custom post types belong in plugins