{"id":133085,"date":"2014-10-22T08:00:00","date_gmt":"2014-10-22T12:00:00","guid":{"rendered":"http:\/\/premium.wpmudev.org\/blog\/?p=133085"},"modified":"2016-05-18T06:43:32","modified_gmt":"2016-05-18T06:43:32","slug":"create-wordpress-custom-post-types","status":"publish","type":"post","link":"https:\/\/wpmudev.com\/blog\/create-wordpress-custom-post-types\/","title":{"rendered":"How to Create WordPress Custom Post Types"},"content":{"rendered":"<p>The custom post type feature is what transforms WordPress from a blogging platform to a fully-fledged content management system.<\/p>\n<p>Post types cannot be created and managed from the admin but can be exposed very easily using a little code.<\/p>\n<p>In this article, I&#8217;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&#8217;s begin.<\/p>\n<h2>What is a Post?<\/h2>\n<p>Before getting into custom post types I think we should clarify some terminology since the naming convention results in a bit of confusion.<\/p>\n<p>It is best to think of a <strong>post<\/strong> as a unit of content stored in the database. Each post has a <strong>post_type<\/strong> property, which defines what type of content it is. Here&#8217;s where things get tricky. One of the most commonly used built-in post types is named &#8220;post.&#8221;<\/p>\n<p>This means that &#8220;post&#8221; 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&#8217; blog post.<\/p>\n<p>For the purposes of this article I will be using &#8220;post&#8221; in its general meaning of an entry in the database. When talking about posts with a post type of post I will use the &#8220;blog post&#8221; convention.<\/p>\n<h2>Custom Post Types<\/h2>\n<p>In essence, a custom post type is nothing more than a change in the <code>post_type<\/code> attribute of a post. If you publish a blog post on your website and go into the database to\u00a0rewrite the post type from &#8220;post&#8221; to &#8220;page&#8221; the post will now show up in the pages section in the admin.<\/p>\n<p>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\u00a0can be set for each custom post type.<\/p>\n<p>So what is a good example of a custom post type? Let&#8217;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 &#8220;recipe&#8221; post type.<\/p>\n<figure id=\"attachment_133289\" class=\"wp-caption aligncenter\" data-caption=\"true\"><a rel=\"lightbox[133085]\" class=\"blog-thumbnail\" href=\"https:\/\/wpmudev.com\/blog\/wp-content\/uploads\/2014\/10\/recipes.jpg\" target=\"_blank\"><img loading=\"lazy\" decoding=\"async\" class=\"size-full wp-image-133289\" src=\"https:\/\/wpmudev.com\/blog\/wp-content\/uploads\/2014\/10\/recipes.jpg\" alt=\"Recipes post type\" width=\"700\" height=\"192\" \/><\/a><figcaption class=\"wp-caption-text\">A newly created recipes post type.<\/figcaption><\/figure>\n<h2>When To Use Custom Post Types<\/h2>\n<p>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 &#8220;Recipes&#8221; category.<\/p>\n<p>So where&#8217;s the line? When should we use categories and when are custom post types more appropriate?<\/p>\n<p>There really isn&#8217;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:<\/p>\n<ul>\n<li>If you publish at least two very different types of content. For example, personal blog entries and recipes.<\/li>\n<li>If it would be better to visually and structurally distinguish a specific type of content. For example: personal blog and your illustration portfolio.<\/li>\n<li>If a type of content doesn&#8217;t fit in a chronological order. For example, a company blog and company style guides.<\/li>\n<li>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.<\/li>\n<li>If using categories and tags would lead to over-complicated taxonomies. For example, a personal blog and movie reviews.<\/li>\n<\/ul>\n<h2>Built-In Post Types<\/h2>\n<p>Now that we understand a bit about custom post types, let&#8217;s take a look at the post types WordPress uses by default. Many people know about posts (<code>post<\/code>) and pages (<code>page<\/code>) but did you know that uploaded images are also posts? They use the <code>attachment<\/code> post type.<\/p>\n<p>Surprisingly enough there are two more: revisions (<code>revision<\/code>) and navigation menus (<code>nav_menu_item<\/code>). 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.<\/p>\n<h2>Creating A Custom Post Type<\/h2>\n<p>Enough talk! Let&#8217;s actually create a custom post type. All the code in this article is best placed in a plugin. If you&#8217;d like to just give it a quick go you can place it in your theme&#8217;s functions file, but I advise moving it to a plugin for production use.<\/p>\n<p>To create \u2013 and highly customize \u2013 a post type you would only need a single function: <code>register_post_type()<\/code>. <a href=\"http:\/\/codex.wordpress.org\/Function_Reference\/register_post_type\" target=\"_blank\">The documentation<\/a> for it is pretty hefty but allows for some great modifications. To register a very simple custom post type you&#8217;ll only need a couple of lines:<\/p>\n<div class=\"gist\" data-gist=\"07d786562e6b2d34ee5bc80e5c112963\" data-gist-file=\"custom-post-type-simple.php\"><a class=\"loading\" href=\"https:\/\/gist.github.com\/07d786562e6b2d34ee5bc80e5c112963.js?file=custom-post-type-simple.php\">Loading gist 07d786562e6b2d34ee5bc80e5c112963<\/a><div class=\"gist-consent-notice\" style=\"display:none\"><p>Please <a href=\"javascript:Cookiebot.renew()\">update your cookie preferences<\/a> to enable preference cookies to view this gist.<\/p><\/div><\/div>\n<p>Let&#8217;s analyze the basics. Notice that <code>register_post_type()<\/code> is used in a function that is hooked to the <code>init<\/code> 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&#8217;ll be looking at the arguments in a detail soon.<\/p>\n<h2>Customizing Post Types<\/h2>\n<p>There are three main things you can do to customize your post types:<\/p>\n<ul>\n<li>Modify the arguments of the <code>register_post_type()<\/code> function<\/li>\n<li>Add custom interaction messages (post deleted, updated, etc.)<\/li>\n<li>Adding help sections to various screens in the custom post type admin<\/li>\n<\/ul>\n<p>Let&#8217;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.<\/p>\n<h3>Description and Labels<\/h3>\n<p>If you use the simple method of registering a post type you&#8217;ll notice that buttons still say things like &#8220;New Post&#8221; and &#8220;Delete Post&#8221;. These can be customized using the <code>labels<\/code> property of the argument array.<\/p>\n<div class=\"gist\" data-gist=\"fb244a9f2101a6fb0684c23e209d48ba\" data-gist-file=\"custom-post-type-labels.php\"><a class=\"loading\" href=\"https:\/\/gist.github.com\/fb244a9f2101a6fb0684c23e209d48ba.js?file=custom-post-type-labels.php\">Loading gist fb244a9f2101a6fb0684c23e209d48ba<\/a><div class=\"gist-consent-notice\" style=\"display:none\"><p>Please <a href=\"javascript:Cookiebot.renew()\">update your cookie preferences<\/a> to enable preference cookies to view this gist.<\/p><\/div><\/div>\n<p>Note that in the previous example we used the <code>label<\/code> property, this example uses <code>labels<\/code>. Make sure to use translation functions if your work will be for public consumption.<\/p>\n<h3>Post Type Visibility<\/h3>\n<p>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 <code>public<\/code> parameter, which sets the values of other properties in one go.<\/p>\n<p>If it is set to <code>true<\/code>, 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.<\/p>\n<p>If it is set to <code>false<\/code>, 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.<\/p>\n<p>For more granular control you can specify properties separately. The value of all the parameters below is the same as the value of the <code>public<\/code> parameter, except for\u00a0<code>exclude_from_search<\/code>. The value of this property is the opposite.<\/p>\n<ul>\n<li><code>public<\/code><\/li>\n<li><code>exclude_from_search<\/code><\/li>\n<li><code>publicly_queryable<\/code><\/li>\n<li><code>show_ui<\/code><\/li>\n<li><code>show_in_nav_menus<\/code><\/li>\n<li><code>show_in_admin_bar<\/code><\/li>\n<\/ul>\n<div class=\"gist\" data-gist=\"c82f08db16e875745b4b2ee492ef1db3\" data-gist-file=\"custom-post-type-visibility.php\"><a class=\"loading\" href=\"https:\/\/gist.github.com\/c82f08db16e875745b4b2ee492ef1db3.js?file=custom-post-type-visibility.php\">Loading gist c82f08db16e875745b4b2ee492ef1db3<\/a><div class=\"gist-consent-notice\" style=\"display:none\"><p>Please <a href=\"javascript:Cookiebot.renew()\">update your cookie preferences<\/a> to enable preference cookies to view this gist.<\/p><\/div><\/div>\n<p>This example creates a post type which holds our notes on customers. We probably don&#8217;t want this to be visible in any way on the front end so I&#8217;ve made sure the <code>public<\/code> property is false. We\u00a0<strong>do<\/strong> want to be able to manage them in the backend so I&#8217;ve set the <code>show_ui<\/code> and <code>show_in_admin_bar<\/code> properties to &#8220;true.&#8221;<\/p>\n<h3>Menu Customization<\/h3>\n<p>You can use three functions to modify the behaviour of the menu entry for your custom post type. <code>show_in_menu<\/code> 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 <code>upload.php<\/code> to add it as a sub-menu.<\/p>\n<p>The <code>menu_position<\/code> property sets where the menu shows up in the top-level list. Take a look at the <a href=\"http:\/\/codex.wordpress.org\/Function_Reference\/register_post_type\" target=\"_blank\">Codex<\/a> for the numbers to use for specific placements.<\/p>\n<p>Finally, the <code>menu_icon<\/code> 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 <a href=\"https:\/\/developer.wordpress.org\/resource\/dashicons\/\" target=\"_blank\">Dashicons<\/a> which now ships with WordPress.<\/p>\n<div class=\"gist\" data-gist=\"84e1496e41cc425c9aedaa2cd45bce6f\" data-gist-file=\"custom-post-type-menus.php\"><a class=\"loading\" href=\"https:\/\/gist.github.com\/84e1496e41cc425c9aedaa2cd45bce6f.js?file=custom-post-type-menus.php\">Loading gist 84e1496e41cc425c9aedaa2cd45bce6f<\/a><div class=\"gist-consent-notice\" style=\"display:none\"><p>Please <a href=\"javascript:Cookiebot.renew()\">update your cookie preferences<\/a> to enable preference cookies to view this gist.<\/p><\/div><\/div>\n<p>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.<\/p>\n<h3>Configuring Post Type Features<\/h3>\n<p>You can choose a number of features to use or discard for your custom post type. The <code>hierarchical<\/code> property will create a flat structure (like posts) when set to false. If set to &#8220;true,&#8221; you will be able to create parent-child relationships like you can with pages.<\/p>\n<p>The <code>taxonomies<\/code> 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.<\/p>\n<div class=\"gist\" data-gist=\"c4b1e53eb683fcd5351d9c4ebbd51d2a\" data-gist-file=\"custom-post-type-features.php\"><a class=\"loading\" href=\"https:\/\/gist.github.com\/c4b1e53eb683fcd5351d9c4ebbd51d2a.js?file=custom-post-type-features.php\">Loading gist c4b1e53eb683fcd5351d9c4ebbd51d2a<\/a><div class=\"gist-consent-notice\" style=\"display:none\"><p>Please <a href=\"javascript:Cookiebot.renew()\">update your cookie preferences<\/a> to enable preference cookies to view this gist.<\/p><\/div><\/div>\n<p>If you plan on using custom taxonomies you will still need to create the taxonomy with the <a href=\"http:\/\/codex.wordpress.org\/Function_Reference\/register_taxonomy\" target=\"_blank\">register_taxonomy()<\/a> function.<\/p>\n<p>The <code>supports<\/code> 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&#8217;s a list of available options:<\/p>\n<ul>\n<li><code>title<\/code><\/li>\n<li><code>editor<\/code><\/li>\n<li><code>author<\/code><\/li>\n<li><code>thumbnail<\/code><\/li>\n<li><code>excerpt<\/code><\/li>\n<li><code>trackbacks<\/code><\/li>\n<li><code>custom-fields<\/code><\/li>\n<li><code>comments<\/code><\/li>\n<li><code>revisions<\/code><\/li>\n<li><code>page-attributes<\/code><\/li>\n<li><code>post-formats<\/code><\/li>\n<\/ul>\n<h3>Archives and Rewrites<\/h3>\n<p><code>has_archive<\/code>\u00a0is a great property which allows you to create a listing of your post type on the front end automatically. By setting the value to <code>true<\/code> you&#8217;ll find a list of your custom posts at http:\/\/yourdomain.com\/post_type\/. In your theme you can customize this listing using the <code>archive-posttype.php<\/code> file.<\/p>\n<p>A full guide to rewrites is a bit out of the scope of this article, but it&#8217;s handy to know about them. The <code>rewrite<\/code> 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&#8217;t conflict you could use &#8220;my_product&#8221; as the post type and rewrite it to &#8220;product&#8221; in the URL. Here&#8217;s how:<\/p>\n<div class=\"gist\" data-gist=\"2bfada49fba33d258ee00f18cddef446\" data-gist-file=\"custom-post-type-rewrite.php\"><a class=\"loading\" href=\"https:\/\/gist.github.com\/2bfada49fba33d258ee00f18cddef446.js?file=custom-post-type-rewrite.php\">Loading gist 2bfada49fba33d258ee00f18cddef446<\/a><div class=\"gist-consent-notice\" style=\"display:none\"><p>Please <a href=\"javascript:Cookiebot.renew()\">update your cookie preferences<\/a> to enable preference cookies to view this gist.<\/p><\/div><\/div>\n<h2>Post Type Interaction Messages<\/h2>\n<p>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 <code>post_updated_messages<\/code> filter, here&#8217;s how:<\/p>\n<div class=\"gist\" data-gist=\"59ebb35966c9b7b765bc47751a9b68d9\" data-gist-file=\"custom-post-type-messages.php\"><a class=\"loading\" href=\"https:\/\/gist.github.com\/59ebb35966c9b7b765bc47751a9b68d9.js?file=custom-post-type-messages.php\">Loading gist 59ebb35966c9b7b765bc47751a9b68d9<\/a><div class=\"gist-consent-notice\" style=\"display:none\"><p>Please <a href=\"javascript:Cookiebot.renew()\">update your cookie preferences<\/a> to enable preference cookies to view this gist.<\/p><\/div><\/div>\n<p>First of all, note that the <code>$messages<\/code> variable passed to the function contains <strong>all<\/strong> messages. The sub-arrays contain the messages for specific post types. All we need to do is define an array for the\u00a0custom post type with the appropriate messages. Don&#8217;t forget to use translation functions on those messages. I&#8217;ve left them out here for brevity&#8217;s sake.<\/p>\n<h2>Contextual Help<\/h2>\n<p>Ever noticed the help tab in your posts or pages section? If you click there you&#8217;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 \u2013 the\u00a0fewer\u00a0support requests you get, the better.<\/p>\n<p>We&#8217;ll need to use the <code>$screen<\/code> object in the function we hook to <code>admin_head<\/code>. The template for adding help sections is quite straightforward. Here we go:<\/p>\n<div class=\"gist\" data-gist=\"2f4e0fdd7b4052cbc1e3d26798bdfec1\" data-gist-file=\"custom-post-type-help.php\"><a class=\"loading\" href=\"https:\/\/gist.github.com\/2f4e0fdd7b4052cbc1e3d26798bdfec1.js?file=custom-post-type-help.php\">Loading gist 2f4e0fdd7b4052cbc1e3d26798bdfec1<\/a><div class=\"gist-consent-notice\" style=\"display:none\"><p>Please <a href=\"javascript:Cookiebot.renew()\">update your cookie preferences<\/a> to enable preference cookies to view this gist.<\/p><\/div><\/div>\n<p>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 <code>add_help_tab()<\/code> method.<\/p>\n<h2>Putting It All Together<\/h2>\n<p>The code to register a recipe post type, along with interaction messages and help sections would look something like the example below. Don&#8217;t forget the settings you use are ultimately up to what your project requires.<\/p>\n<div class=\"gist\" data-gist=\"08450ef40d7e875a7eb510287d29ec65\" data-gist-file=\"custom-post-type-full.php\"><a class=\"loading\" href=\"https:\/\/gist.github.com\/08450ef40d7e875a7eb510287d29ec65.js?file=custom-post-type-full.php\">Loading gist 08450ef40d7e875a7eb510287d29ec65<\/a><div class=\"gist-consent-notice\" style=\"display:none\"><p>Please <a href=\"javascript:Cookiebot.renew()\">update your cookie preferences<\/a> to enable preference cookies to view this gist.<\/p><\/div><\/div>\n<h2>Overview<\/h2>\n<p>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.<\/p>\n<p>It takes some practice to know when to use post types, when to use taxonomies and \u2013 more importantly \u2013 when <strong>not<\/strong> to use them. The only way to learn is by doing, so go and experiment.<\/p>\n<p>&nbsp;<\/p>\n<p><em>Image credit:\u00a0Icon made by <a title=\"Freepik\" href=\"http:\/\/www.freepik.com\" target=\"_blank\">Freepik<\/a> from <a title=\"Flaticon\" href=\"http:\/\/www.flaticon.com\" target=\"_blank\">www.flaticon.com<\/a> is licensed under <a title=\"Creative Commons BY 3.0\" href=\"http:\/\/creativecommons.org\/licenses\/by\/3.0\/\" target=\"_blank\">CC BY 3.0<\/a>.<\/em><\/p>\n","protected":false},"excerpt":{"rendered":"<p>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&#8217;ll show you how to create a custom post type for recipes and [&hellip;]<\/p>\n","protected":false},"author":344049,"featured_media":208090,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"blog_reading_time":"","wds_primary_category":0,"wds_primary_tutorials_categories":0,"footnotes":""},"categories":[263],"tags":[1041],"tutorials_categories":[],"class_list":["post-133085","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-tutorials","tag-custom-post-types"],"_links":{"self":[{"href":"https:\/\/wpmudev.com\/blog\/wp-json\/wp\/v2\/posts\/133085","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/wpmudev.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/wpmudev.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/wpmudev.com\/blog\/wp-json\/wp\/v2\/users\/344049"}],"replies":[{"embeddable":true,"href":"https:\/\/wpmudev.com\/blog\/wp-json\/wp\/v2\/comments?post=133085"}],"version-history":[{"count":5,"href":"https:\/\/wpmudev.com\/blog\/wp-json\/wp\/v2\/posts\/133085\/revisions"}],"predecessor-version":[{"id":198622,"href":"https:\/\/wpmudev.com\/blog\/wp-json\/wp\/v2\/posts\/133085\/revisions\/198622"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/wpmudev.com\/blog\/wp-json\/wp\/v2\/media\/208090"}],"wp:attachment":[{"href":"https:\/\/wpmudev.com\/blog\/wp-json\/wp\/v2\/media?parent=133085"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/wpmudev.com\/blog\/wp-json\/wp\/v2\/categories?post=133085"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/wpmudev.com\/blog\/wp-json\/wp\/v2\/tags?post=133085"},{"taxonomy":"tutorials_categories","embeddable":true,"href":"https:\/\/wpmudev.com\/blog\/wp-json\/wp\/v2\/tutorials_categories?post=133085"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}