How to Create WordPress Custom Post Types

How to Create WordPress Custom Post Types

The custom post type feature is what transforms WordPress from a blogging platform to a fully-fledged content management system.

Post types cannot be created and managed from the admin but can be exposed very easily using a little code.

In this article, I’ll show you how to create a custom post type for recipes and take you from the very basics to some more complex cases. Let’s begin.

What is a Post?

Before getting into custom post types I think we should clarify some terminology since the naming convention results in a bit of confusion.

It is best to think of a post as a unit of content stored in the database. Each post has a post_type property, which defines what type of content it is. Here’s where things get tricky. One of the most commonly used built-in post types is named “post.”

This means that “post” can refer to any post entered into WordPress (whether this is a blog post, a page or a custom type), but it can also refer to a regular ol’ blog post.

For the purposes of this article I will be using “post” in its general meaning of an entry in the database. When talking about posts with a post type of post I will use the “blog post” convention.

Custom Post Types

In essence, a custom post type is nothing more than a change in the post_type attribute of a post. If you publish a blog post on your website and go into the database to rewrite the post type from “post” to “page” the post will now show up in the pages section in the admin.

WordPress defines a bunch of properties for each post type. Is the particular post type searchable? Is it visible in the admin? Can categories and tags be assigned to it, does it allow comments? A number of these features can be set for each custom post type.

So what is a good example of a custom post type? Let’s assume you love food and cooking. Chances are you have a website where you write about your personal things, but now and then you publish a recipe. A recipe is a very different type of content than a personal blog entry. In this case it may be appropriate to create a “recipe” post type.

Recipes post type
A newly created recipes post type.

When To Use Custom Post Types

It may be difficult to figure out when a custom post type is needed. Sometimes you can get by with categories. In our previous example we could have created a “Recipes” category.

So where’s the line? When should we use categories and when are custom post types more appropriate?

There really isn’t a set-in-stone rule here. It comes down to practicality, personal preference and how your theme is built. There are some good guidelines though; if it seems like some of these apply to you, a custom post type might be in order:

  • If you publish at least two very different types of content. For example, personal blog entries and recipes.
  • If it would be better to visually and structurally distinguish a specific type of content. For example: personal blog and your illustration portfolio.
  • If a type of content doesn’t fit in a chronological order. For example, a company blog and company style guides.
  • If a content type could easily be separated out into a different website and still remain coherent. For example, a personal blog and sold products.
  • If using categories and tags would lead to over-complicated taxonomies. For example, a personal blog and movie reviews.

Built-In Post Types

Now that we understand a bit about custom post types, let’s take a look at the post types WordPress uses by default. Many people know about posts (post) and pages (page) but did you know that uploaded images are also posts? They use the attachment post type.

Surprisingly enough there are two more: revisions (revision) and navigation menus (nav_menu_item). Revisions are just like posts but they contain the data about past versions of posts. Navigation menu items hold information about each separate item in the navigation system.

Creating A Custom Post Type

Enough talk! Let’s actually create a custom post type. All the code in this article is best placed in a plugin. If you’d like to just give it a quick go you can place it in your theme’s functions file, but I advise moving it to a plugin for production use.

To create – and highly customize – a post type you would only need a single function: register_post_type(). The documentation for it is pretty hefty but allows for some great modifications. To register a very simple custom post type you’ll only need a couple of lines:

Let’s analyze the basics. Notice that register_post_type() is used in a function that is hooked to the init action. It takes two arguments: the custom post type and an arguments array. The custom post type should be 20 characters at most and must not contain any spaces or capital letters. I also recommend writing singular forms (post, page, recipe, book, etc.). We’ll be looking at the arguments in a detail soon.

Customizing Post Types

There are three main things you can do to customize your post types:

  • Modify the arguments of the register_post_type() function
  • Add custom interaction messages (post deleted, updated, etc.)
  • Adding help sections to various screens in the custom post type admin

Let’s take a look at the most common arguments you can change when you register the post type and then turn our attention to the interaction messages and help text.

Description and Labels

If you use the simple method of registering a post type you’ll notice that buttons still say things like “New Post” and “Delete Post”. These can be customized using the labels property of the argument array.

Note that in the previous example we used the label property, this example uses labels. Make sure to use translation functions if your work will be for public consumption.

Post Type Visibility

There are a number of parameters which allow you to fine-tune the visibility of your custom post type in the front and backend. The most prominent one is the public parameter, which sets the values of other properties in one go.

If it is set to true, the post type will be included in searches, the UI will be shown, it will show up in the admin bar and so on. This is similar to how built in pages and posts work.

If it is set to false, the post type is excluded from searches, it will not show up in the UI, it will be hidden in the menus and so on. This is like the built-in revision post type.

For more granular control you can specify properties separately. The value of all the parameters below is the same as the value of the public parameter, except for exclude_from_search. The value of this property is the opposite.

  • public
  • exclude_from_search
  • publicly_queryable
  • show_ui
  • show_in_nav_menus
  • show_in_admin_bar

This example creates a post type which holds our notes on customers. We probably don’t want this to be visible in any way on the front end so I’ve made sure the public property is false. We do want to be able to manage them in the backend so I’ve set the show_ui and show_in_admin_bar properties to “true.”

Menu Customization

You can use three functions to modify the behaviour of the menu entry for your custom post type. show_in_menu sets where the menu is shown. If set to false the menu entry is not shown. If set to true it will be shown as a top level menu. You can set it to an existing top level page like upload.php to add it as a sub-menu.

The menu_position property sets where the menu shows up in the top-level list. Take a look at the Codex for the numbers to use for specific placements.

Finally, the menu_icon parameter allows you to set an icon. You can add a URL to an icon, or you can use the name of an icon from Dashicons which now ships with WordPress.

The code above will add our post type as a top level menu entry into position 20 (which is just below pages) and It will use the carrot icon from the Dashicons set.

Configuring Post Type Features

You can choose a number of features to use or discard for your custom post type. The hierarchical property will create a flat structure (like posts) when set to false. If set to “true,” you will be able to create parent-child relationships like you can with pages.

The taxonomies property allows you to assign custom taxonomies to the post type. This is an array of taxonomy slugs. The following example creates a hierarchical post type with support for tags.

If you plan on using custom taxonomies you will still need to create the taxonomy with the register_taxonomy() function.

The supports property holds an array of features which the post type supports. These have an effect on the admin user interface and on some parts of the front end as well. Here’s a list of available options:

  • title
  • editor
  • author
  • thumbnail
  • excerpt
  • trackbacks
  • custom-fields
  • comments
  • revisions
  • page-attributes
  • post-formats

Archives and Rewrites

has_archive is a great property which allows you to create a listing of your post type on the front end automatically. By setting the value to true you’ll find a list of your custom posts at In your theme you can customize this listing using the archive-posttype.php file.

A full guide to rewrites is a bit out of the scope of this article, but it’s handy to know about them. The rewrite property defines how post type URLs should be handled. A good use-case is if you are creating a post type for a common task, say products. To make sure your plugin doesn’t conflict you could use “my_product” as the post type and rewrite it to “product” in the URL. Here’s how:

Post Type Interaction Messages

Whenever you perform an action on a post (saving, deleting, searching, etc.) you receive messages which give you feedback about your action. These messages can be tailored to the post type using the post_updated_messages filter, here’s how:

First of all, note that the $messages variable passed to the function contains all messages. The sub-arrays contain the messages for specific post types. All we need to do is define an array for the custom post type with the appropriate messages. Don’t forget to use translation functions on those messages. I’ve left them out here for brevity’s sake.

Contextual Help

Ever noticed the help tab in your posts or pages section? If you click there you’ll see that you can add a great little help section split into tabs. Adding contextual help is extremely important as it allows users to get help on the spot. This is better for them and better for you as well – the fewer support requests you get, the better.

We’ll need to use the $screen object in the function we hook to admin_head. The template for adding help sections is quite straightforward. Here we go:

The first thing we do is check the current screen. If we are not on the main post type screen we return early. If we are on the correct screen we can create our help tabs. Each help tab consists of a unique ID, a unique name and the content of the tab. These can then be registered individually with the add_help_tab() method.

Putting It All Together

The code to register a recipe post type, along with interaction messages and help sections would look something like the example below. Don’t forget the settings you use are ultimately up to what your project requires.


By now you should know what a post type is, how to create one customized to your needs and how to add messages and a help section to it.

It takes some practice to know when to use post types, when to use taxonomies and – more importantly – when not to use them. The only way to learn is by doing, so go and experiment.

If you have any great tips and tricks about custom post type or some great ways you’ve used them, please share in the comments below.

Image credit: Icon made by Freepik from is licensed under CC BY 3.0.