How to Create Custom Post Types and Taxonomies in WordPress

In this step-by-step tutorial I’ll show you how to create custom post types and taxonomies in WordPress. What are custom post types? Custom post types are posts with a different post_type value in database. WordPress has five post types ready for you: post, page, attachment, revision, and nav_menu_item. You can create custom post types for books, events, movies, products, reviews, and so on. In this tutorial I’ll show you how to create a custom post type that will hold the items of a portfolio. What are custom taxonomies? Despite the name, custom taxonomies are just like categories and tags, a way to organize posts. Now, let’s get started, shall we?

Things that you must do:

  1. Install WordPress
  2. Open the functions.php file located in the directory of your WordPress theme.
    1. Find the load_theme_textdomain() function. The first parameter of that function is your text_domain.
  3. In the code below, don’t forget to replace:
    1. prefix_ with your prefix. The name of my WordPress theme is tutsmix. I could use tm_ as prefix. We use a prefix to avoid conflicts because we can’t have, for example, two functions with the same name.
    2. text_domain with your text_domain.

1. How to Create Custom Post Types

Please note that the code below goes into the functions.php file.

/**
 * Registers a custom post type.
 *
 * @link http://codex.wordpress.org/Function_Reference/register_post_type
 */
function prefix_register_post_type()
{
	register_post_type(
		'prefix_portfolio',
		array(
			'labels'        => array(
				'name'               => __('Portfolio', 'text_domain'),
				'singular_name'      => __('Portfolio', 'text_domain'),
				'menu_name'          => __('Portfolio', 'text_domain'),
				'name_admin_bar'     => __('Portfolio Item', 'text_domain'),
				'all_items'          => __('All Items', 'text_domain'),
				'add_new'            => _x('Add New', 'prefix_portfolio', 'text_domain'),
				'add_new_item'       => __('Add New Item', 'text_domain'),
				'edit_item'          => __('Edit Item', 'text_domain'),
				'new_item'           => __('New Item', 'text_domain'),
				'view_item'          => __('View Item', 'text_domain'),
				'search_items'       => __('Search Items', 'text_domain'),
				'not_found'          => __('No items found.', 'text_domain'),
				'not_found_in_trash' => __('No items found in Trash.', 'text_domain'),
				'parent_item_colon'  => __('Parent Items:', 'text_domain'),
			),
			'public'        => true,
			'menu_position' => 5,
			'supports'      => array(
				'title',
				'editor',
				'thumbnail',
				'excerpt',
				'custom-fields',
			),
			'taxonomies'    => array(
				'prefix_portfolio_categories',
			),
			'has_archive'   => true,
			'rewrite'       => array(
				'slug' => 'portfolio',
			),
		)
	);
}

add_action('init', 'prefix_register_post_type');

Above we’ve created the prefix_register_post_type() function, and inside it we’ve registered a custom post type using the register_post_type() function which accepts two parameters. The first parameter is the name of the custom post type which shouldn’t be longer than 20 characters, the second is the array of arguments. In the array of arguments we’ve defined a set of labels and the behavior of the custom post type. After that, we’ve attached the prefix_register_post_type() function to the init action hook. Now you should have a new entry called “Portfolio” on the left menu of your Dashboard. That’s awesome, right? Please note that a refresh may be needed. Let’s move on to the next step.

2. How to Display Custom Update Messages

Please note that the code below goes into the functions.php file.

/**
 * Sets the custom update messages for a custom post type.
 *
 * @param   array  $messages
 * @return  array
 * @link    http://codex.wordpress.org/Function_Reference/register_post_type
 */
function prefix_post_updated_messages($messages)
{
	$post             = get_post();
	$post_type        = get_post_type($post);
	$post_type_object = get_post_type_object($post_type);

	$messages['prefix_portfolio'] = array(
		0  => '',
		1  => __('Item updated.', 'text_domain'),
		2  => __('Custom field updated.', 'text_domain'),
		3  => __('Custom field deleted.', 'text_domain'),
		4  => __('Item updated.', 'text_domain'),
		5  => isset($_GET['revision']) ? sprintf(__('Item restored to revision from %s', 'text_domain'), wp_post_revision_title( (int) $_GET['revision'], false)) : false,
		6  => __('Item published.', 'text_domain'),
		7  => __('Item saved.', 'text_domain'),
		8  => __('Item submitted.', 'text_domain'),
		9  => sprintf(__('Item scheduled for: <strong>%1$s</strong>.', 'text_domain'), date_i18n(__('M j, Y @ G:i', 'text_domain'), strtotime($post->post_date))),
		10 => __('Item draft updated.', 'text_domain'),
	);

	if ($post_type_object->publicly_queryable)
	{
		$permalink         = get_permalink($post->ID);
		$preview_permalink = add_query_arg('preview', 'true', $permalink);

		switch ($post_type)
		{
			case 'prefix_portfolio':
				$view_link    = sprintf(' <a href="%s">%s</a>', esc_url($permalink), __('View item', 'text_domain'));
				$preview_link = sprintf(' <a href="%s" target="_blank">%s</a>', esc_url($preview_permalink), __('Preview item', 'text_domain'));
			break;
		}

		if (isset($view_link))
		{
			$messages[$post_type][1] .= $view_link;
			$messages[$post_type][6] .= $view_link;
			$messages[$post_type][9] .= $view_link;
		}

		if (isset($preview_link))
		{
			$messages[$post_type][8]  .= $preview_link;
			$messages[$post_type][10] .= $preview_link;
		}
	}

	return $messages;
}

add_filter('post_updated_messages', 'prefix_post_updated_messages');

What are custom update messages? Custom update messages are the messages that WordPress displays when you publish a post, update one, and so on. Those messages are tailored to regular posts. The posts in our custom post type are called “items”. We must define custom update messages because we don’t want to see, for example, “Post published.” instead of “Item published.”.

Above we’ve created the prefix_post_updated_messages() function, and inside it we’ve defined the custom update messages. After that, we’ve attached the prefix_post_updated_messages() function to the post_updated_messages filter hook. Now, for example, if you publish a post, you should see a custom update message. Are you ready to move on to the next step? Let’s do this then.

3. How to Create Custom Taxonomies

Please note that the code below goes into the functions.php file.

/**
 * Registers a custom taxonomy.
 *
 * @link http://codex.wordpress.org/Function_Reference/register_taxonomy
 */
function prefix_register_taxonomy()
{
	register_taxonomy(
		'prefix_portfolio_categories',
		array(
			'prefix_portfolio',
		),
		array(
			'labels'            => array(
				'name'              => _x('Categories', 'prefix_portfolio', 'text_domain'),
				'singular_name'     => _x('Category', 'prefix_portfolio', 'text_domain'),
				'menu_name'         => __('Categories', 'text_domain'),
				'all_items'         => __('All Categories', 'text_domain'),
				'edit_item'         => __('Edit Category', 'text_domain'),
				'view_item'         => __('View Category', 'text_domain'),
				'update_item'       => __('Update Category', 'text_domain'),
				'add_new_item'      => __('Add New Category', 'text_domain'),
				'new_item_name'     => __('New Category Name', 'text_domain'),
				'parent_item'       => __('Parent Category', 'text_domain'),
				'parent_item_colon' => __('Parent Category:', 'text_domain'),
				'search_items'      => __('Search Categories', 'text_domain'),
			),
			'show_admin_column' => true,
			'hierarchical'      => true,
			'rewrite'           => array(
				'slug' => 'portfolio/category',
			),
		)
	);
}

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

Above we’ve created the prefix_register_taxonomy() function, and inside it we’ve registered a custom taxonomy using the register_taxonomy() function which accepts three parameters. The first parameter is the name of the custom taxonomy which shouldn’t be longer than 32 characters, the second is the name of the custom post type, and the third is the array of arguments. In the array of arguments we’ve defined a set of labels and the behavior of the custom taxonomy. After that, we’ve attached the prefix_register_taxonomy() function to the init action hook. Now you should have a new entry called “Categories” on the “Portfolio” section of your Dashboard. Please note that a refresh may be needed. Are you still with me? Alright, let’s move on then.

4. How to Flush the Rewrite Rules

Please note that the code below goes into the functions.php file.

/**
 * Flushes the rewrite rules.
 *
 * @link http://codex.wordpress.org/Function_Reference/register_post_type
 */
function prefix_flush_rewrite_rules()
{
	flush_rewrite_rules();
}

add_action('after_switch_theme', 'prefix_flush_rewrite_rules');

Custom post types get their own URL (e.g. http://example.com/post_type/). http://example.com/prefix_portfolio/ in our case, but because we’ve defined a custom slug, that URL becomes http://example.com/portfolio/.

Custom taxonomies get their own URL too (e.g. http://example.com/taxonomy/). http://example.com/prefix_portfolio_categories/ in our case, but because we’ve defined a custom slug, that URL becomes http://example.com/portfolio/category/.

For custom slugs to work, we must flush the rewrite rules.

Above we’ve created the prefix_flush_rewrite_rules() function, and inside it we’ve called the flush_rewrite_rules() function, which will flush the rewrite rules. After that, we’ve attached the prefix_flush_rewrite_rules() function to the after_switch_theme action hook. Now all you have to do is to reactivate your WordPress theme or to visit the “Settings → Permalinks” section of your Dashboard. After that, everything should work smoothly. We’re almost done. Hang in there!

5. How to Display the Posts of a Custom Post Type

To display the posts of a custom post type, WordPress will use the first file that it finds in this order:

  1. archive-post_type.php (e.g. archive-prefix_portfolio.php)
  2. archive.php
  3. index.php

To display single posts, WordPress will use the first file that it finds in this order:

  1. single-post_type.php (e.g. single-prefix_portfolio.php)
  2. single.php
  3. index.php

You can use the code below to display the posts of a custom post type using the archive-post_type.php file.

<?php if (have_posts()): ?>
	<?php while (have_posts()): ?>
		<?php the_post(); ?>
		<h2 class="entry-title"><a href="<?php the_permalink(); ?>" rel="bookmark"><?php the_title(); ?></a></h2>

		<div class="entry-content"><?php the_content(); ?></div>
	<?php endwhile; ?>
<?php else: ?>
	<h1 class="page-title">No Posts Found</h1>
<?php endif; ?>

You can use the code below to display single posts using the single-post_type.php file.

<?php while (have_posts()): ?>
	<?php the_post(); ?>
	<h1 class="entry-title"><?php the_title(); ?></h1>

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

You can use the code below to display the posts of a custom post type using a custom query with the WP_Query class.

<?php

$prefix_posts = new WP_Query(
	array(
		'post_type' => 'prefix_portfolio',
	)
);

?>
<?php if ($prefix_posts->have_posts()): ?>
	<?php while ($prefix_posts->have_posts()): ?>
		<?php $prefix_posts->the_post(); ?>
		<h2 class="entry-title"><a href="<?php the_permalink(); ?>" rel="bookmark"><?php the_title(); ?></a></h2>

		<div class="entry-content"><?php the_content(); ?></div>
	<?php endwhile; ?>
<?php else: ?>
	<h1 class="page-title">No Posts Found</h1>
<?php endif; ?>
<?php wp_reset_postdata(); ?>

You can use the code below to display the posts of a custom post type from certain taxonomies using a custom query with the WP_Query class.

<?php

$prefix_posts = new WP_Query(
	array(
		'post_type' => 'prefix_portfolio',
		'tax_query' => array(
			array(
				'taxonomy' => 'prefix_portfolio_categories',
				'field'    => 'slug',
				'terms'    => array(
					'branding',
				),
			),
		),
	)
);

?>
<?php if ($prefix_posts->have_posts()): ?>
	<?php while ($prefix_posts->have_posts()): ?>
		<?php $prefix_posts->the_post(); ?>
		<h2 class="entry-title"><a href="<?php the_permalink(); ?>" rel="bookmark"><?php the_title(); ?></a></h2>

		<div class="entry-content"><?php the_content(); ?></div>
	<?php endwhile; ?>
<?php else: ?>
	<h1 class="page-title">No Posts Found</h1>
<?php endif; ?>
<?php wp_reset_postdata(); ?>

6. How to Display the Posts of a Custom Taxonomy

To display the posts of a custom taxonomy, WordPress will use the first file that it finds in this order:

  1. taxonomy-taxonomy-term.php (e.g. taxonomy-prefix_portfolio_categories-branding.php)
  2. taxonomy-taxonomy.php (e.g. taxonomy-prefix_portfolio_categories.php)
  3. taxonomy.php
  4. archive.php
  5. index.php

You can use the code below to display the posts of a custom taxonomy using the taxonomy-taxonomy.php file.

<?php if (have_posts()): ?>
	<?php while (have_posts()): ?>
		<?php the_post(); ?>
		<h2 class="entry-title"><a href="<?php the_permalink(); ?>" rel="bookmark"><?php the_title(); ?></a></h2>

		<div class="entry-content"><?php the_content(); ?></div>
	<?php endwhile; ?>
<?php else: ?>
	<h1 class="page-title">No Posts Found</h1>
<?php endif; ?>

You can use the code below to display the posts of a custom taxonomy using a custom query with the WP_Query class.

<?php

$prefix_posts = new WP_Query(
	array(
		'tax_query' => array(
			array(
				'taxonomy' => 'prefix_portfolio_categories',
				'field'    => 'slug',
				'terms'    => array(
					'branding',
				),
			),
		),
	)
);

?>
<?php if ($prefix_posts->have_posts()): ?>
	<?php while ($prefix_posts->have_posts()): ?>
		<?php $prefix_posts->the_post(); ?>
		<h2 class="entry-title"><a href="<?php the_permalink(); ?>" rel="bookmark"><?php the_title(); ?></a></h2>

		<div class="entry-content"><?php the_content(); ?></div>
	<?php endwhile; ?>
<?php else: ?>
	<h1 class="page-title">No Posts Found</h1>
<?php endif; ?>
<?php wp_reset_postdata(); ?>

Final Thoughts

We’re done. You’ve learned how to create custom post types and taxonomies in WordPress. You’ve also learned how to display their posts using templates and custom queries. I’m very proud of you. I’ve tried to guide you as best I could, and I hope that this step-by-step tutorial was helpful to you.

Add a Comment

Your email address will not be published. Required fields are marked *

Enjoy best web development services at an affordable price. Looking forward to build a good relationship and serve you better...

ABOUT CODINGACE

My name is Nohman Habib and I am a web developer with over 10 years of experience, programming in Joomla, Wordpress, WHMCS, vTiger and Hybrid Apps. My plan to start codingace.com is to share my experience and expertise with others. Here my basic area of focus is to post tutorials primarily on Joomla development, HTML5, CSS3 and PHP.

Nohman Habib

CEO: codingace.com

Request a Quote