Put Some DIY Into Your CPTs

We’ve introduced the idea of Custom Post Types. We’ve talked about some plugins which can build them for us. Now, let’s talk about how to make, or register Custom Post Types within a plugin that you will make your very own self. It sounds scary, but I promise it isn’t.

Create Your Plugin

First, create an empty folder for your plugin. Call it whatever you want, but make it unique to yourself. You do not want to end up having your custom plugin overwritten by a WordPress plugin update of someone else’s plugin with the same name! So, you might call your plugin folder robincornett-custom-post-type. I mean, don’t call it that, please, because I’d feel awkward, but that can be an example for you. I’ve done a quick restaurant menu plugin as an example for this post, and called it Great Dishes, so my folder is called great-dishes (violating the unique name I just told you to use, but I’m not actually using this anywhere, so I can).

Inside your new folder, create a php file with the same name as your folder (so mine is great-dishes.php). The first thing you need to do is add your plugin header information. Mine looks like this (RSS subscribers, this is the first of several embedded gists, so you may just want to come on over to the site to see them):

* Great Dishes is a dead simple plugin to make a menu for a restaurant.
* @package Great Dishes
* @author Robin Cornett <hello@robincornett.com>
* @copyright 2015 Robin Cornett
* @license GPL-2.0+
* @link http://robincornett.com
* @wordpress-plugin
* Plugin Name: Great Dishes
* Plugin URI: https://github.com/robincornett/
* Description: Great Dishes is a dead simple plugin to make a menu for a restaurant.
* Version: 1.0.0
* Author: Robin Cornett
* Author URI: http://robincornett.com
* Text Domain: great-dishes
* License: GPL-2.0+
* License URI: http://www.gnu.org/licenses/gpl-2.0.txt
* Domain Path: /languages

Not all of this is completely necessary, but a lot of it needs to be there so that WordPress can see your plugin, and so that you can activate it. Actually, if you save this file, you can activate it already, although nothing interesting will happen since there is no actual code.

Register Your Custom Post Type

Now it’s time to register your actual post type. In the WordPress Codex, you’ll see a super simple example of how to do this. The register_post_type function for WordPress can take a lot of arguments (or options), but only a few of them are absolutely necessary. The example for Books uses just two.

Note: keep your finger on that spot in the Codex, because that entire page is full of great information.

You’ve got the beginning of your plugin set up and going, so your next step is to actually register your new post type. Now, there are a ton of arguments, or options for your CPT, but don’t get intimidated! You can use this neat online tool called Generate WP, which gives you the ease of some of the plugin options we discussed last time, but actually generates code you can drop right into your plugin.

Here’s my Great Dishes post type:

// Hook into the 'init' action
add_action( 'init', 'great_dishes_register_post_type' );
// Register Custom Post Type
function great_dishes_register_post_type() {
$labels = array(
'name' => _x( 'Great Dishes', 'Post Type General Name', 'great-dishes' ),
'singular_name' => _x( 'Dish', 'Post Type Singular Name', 'great-dishes' ),
'menu_name' => __( 'Great Dishes', 'great-dishes' ),
'parent_item_colon' => __( 'Parent Dish:', 'great-dishes' ),
'all_items' => __( 'All Dishes', 'great-dishes' ),
'view_item' => __( 'View Dish', 'great-dishes' ),
'add_new_item' => __( 'Add New Dish', 'great-dishes' ),
'add_new' => __( 'Add New', 'great-dishes' ),
'edit_item' => __( 'Edit Dish', 'great-dishes' ),
'update_item' => __( 'Update Dish', 'great-dishes' ),
'search_items' => __( 'Search Item', 'great-dishes' ),
'not_found' => __( 'Not found', 'great-dishes' ),
'not_found_in_trash' => __( 'Not found in Trash', 'great-dishes' ),
$rewrite = array(
'slug' => 'menu',
'with_front' => true,
'pages' => true,
'feeds' => false,
$args = array(
'label' => __( 'Great Dishes', 'great-dishes' ),
'description' => __( 'Great Dishes', 'great-dishes' ),
'labels' => $labels,
'supports' => array( 'title', 'editor', 'thumbnail', 'page-attributes', 'genesis-cpt-archives-settings' ),
'taxonomies' => array( 'course' ),
'hierarchical' => true,
'menu_icon' => 'dashicons-heart',
'public' => true,
'show_ui' => true,
'show_in_menu' => true,
'show_in_nav_menus' => true,
'show_in_admin_bar' => true,
'menu_position' => 5,
'can_export' => true,
'has_archive' => true,
'exclude_from_search' => false,
'publicly_queryable' => true,
'rewrite' => $rewrite,
'capability_type' => 'page',
register_post_type( 'great_dish', $args );

Since you’ve kept your finger in the Codex, you can review what each line means, but here are some of the high points:

  • $labels: these are just how WordPress communicates with you about your new post type. If you left them blank, it would continue to say things like “Add New Post”, which isn’t super friendly. The __() wrapped around your labels are for internationalization, if someone were to ever want to translate your plugin. Even if it’s just for you, go ahead and use those, just because you don’t want to have to go back and do them later. The second part of it (in my case, 'great-dishes') needs to be the same slug as your plugin folder.
  • $rewrite: this sets up your rule for how your CPT will show in your links, so mine will be at mycoolsite.com/menu/yummy-dinner/, as a for instance.
  • $args includes $labels, $rewrite, and everything else, including the kitchen sink. Here’s where you can set a neat icon for your post type ('menu_icon'); what kinds of features your post type 'supports' (a featured image, the awesome Genesis archive settings–for Genesis users only, sorry–custom excerpts, etc.; whether it will behave more like posts or pages ('hierarchical'–dated or not dated); even whether it’s public, like posts and pages, or private, like navigation menus.

You’ll notice that mine includes an argument for 'taxonomies', too. Not every custom content type requires a taxonomy, or way of organizing it, but some do. Posts, for example, have two taxonomies: Categories and Tags. A custom post type for sermons might have taxonomies for Preacher, Series, Book of the Bible, Service, and Occasion. My Great Dishes post type will have one taxonomy called Courses–I’m thinking Main Course, Appetizer, Dessert, things like that.

Register a Taxonomy

Of course, before I can start using a taxonomy, I have to register that, too–the line above just nudges WordPress to let it know which taxonomy goes with the post type. So, here’s my taxonomy registration (here’s the Codex link for all possible options/arguments):

// Hook into the 'init' action
add_action( 'init', 'great_dishes_register_taxonomy' );
// Register Custom Taxonomy
function great_dishes_register_taxonomy() {
$labels = array(
'name' => _x( 'Courses', 'Taxonomy General Name', 'great-dishes' ),
'singular_name' => _x( 'Course', 'Taxonomy Singular Name', 'great-dishes' ),
'menu_name' => __( 'Courses', 'great-dishes' ),
'all_items' => __( 'All Courses', 'great-dishes' ),
'parent_item' => __( 'Parent Course', 'great-dishes' ),
'parent_item_colon' => __( 'Parent Course:', 'great-dishes' ),
'new_item_name' => __( 'New Course', 'great-dishes' ),
'add_new_item' => __( 'Add New Course', 'great-dishes' ),
'edit_item' => __( 'Edit Course', 'great-dishes' ),
'update_item' => __( 'Update Course', 'great-dishes' ),
'separate_items_with_commas' => __( 'Separate courses with commas', 'great-dishes' ),
'search_items' => __( 'Search Courses', 'great-dishes' ),
'add_or_remove_items' => __( 'Add or remove courses', 'great-dishes' ),
'choose_from_most_used' => __( 'Choose from the most used courses', 'great-dishes' ),
'not_found' => __( 'Not Found', 'great-dishes' ),
$args = array(
'labels' => $labels,
'hierarchical' => true,
'public' => true,
'show_ui' => true,
'show_admin_column' => true,
'show_in_nav_menus' => true,
'show_tagcloud' => false,
register_taxonomy( 'course', array( 'great_dish' ), $args );

It’s quite similar to the post type registration. It has a lot of similar $labels. You need to decide if it’s 'hierarchical', like Categories, or more free form, like Tags. You can set special rewrite rules (I did not, so it’s going to default to mycoolsite.com/courses/yummy-dinner, from the ‘name’ I set). You can decide if it’s public or not (if it’s not public, though, your taxonomy archive pages won’t work), and whether it will show in the admin columns, when you view your list of posts/dishes in your post type. Note that when I get to the final line, where I register the taxonomy itself, it includes an array with one item, 'great_dish', which is the post type with which my taxonomy is associated.

One great thing is that almost none of these options are truly permanent, so you can experiment with settings, toggle them off and on, and make sure you end up with what you want. The actual post type and taxonomy shouldn’t be changed, especially once you’ve started adding new content, but everything else mostly can be, even permalinks/slugs, as long as you resave your permalinks.

At this point, if you’ve used the code I’ve posted, your WordPress admin will have a new menu item and section called–get ready–Great Dishes. It’s going to look like this:
Great Dishes (Custom Post Type)
If you want, you can absolutely stop here and start adding new [dishes] to your heart’s content. You may need to resave your permalinks before you can see your new Custom Post Type on the front end of your site, but without further instructions from you, they’ll display using your site’s default archive and single templates, so will show similarly to your your posts.

Coming up, we’ll talk about extending your Custom Post Types with custom fields and custom templates.

Reader Interactions


  1. Carolyn says

    This is a great series, I’m learning a lot of something I feel I should know…… Thanks!

  2. Eleanor says

    I’m new to both WordPress and Genesis. Your posts are very helpful. Thanks for putting in the time.

Leave a Reply

Your email address will not be published.

This site uses Akismet to reduce spam. Learn how your comment data is processed.