Menu Search
Jump to the content X X
Smashing Conf Barcelona

You know, we use ad-blockers as well. We gotta keep those servers running though. Did you know that we publish useful books and run friendly conferences — crafted for pros like yourself? E.g. our upcoming SmashingConf Barcelona, dedicated to smart front-end techniques and design patterns.

Extending WordPress With Custom Content Types

WordPress does some pretty amazing things out of the box. It handles content management as well as any other open-source solution out there — and better than many commercial solutions. One of the best attributes of WordPress is its ease of use. It’s easy because there’s not a significant amount of bloat with endless bells and whistles that steepen the learning curve.

On the flip side, some might find WordPress a little… well, light. It does a lot, but not quite enough. If you find yourself hacking WordPress to do the things you wish it would do, then the chances are high that this article is for you.

Further Reading on SmashingMag: Link

WordPress can be easily extended to fit the requirements of a custom data architecture. We’re going to explore the process of registering new data types in a fully compliant manner.

If you want to follow along at home, we’ve provided the full source code5 (TXT, 5.0 KB).

Custom Post Types Link

WordPress gives you a very simple and straightforward way to extend the standard two data types (Posts and Pages) into an endless array for your custom needs. A digital agency or freelancer would need a “Project” post type. A mall would need a “Location” post type.

Quick point. Spinning off custom post types is a great idea for content that is intrinsically different than either Posts or Pages. There could be a case where you would want press releases to live in their own type. But more often than not, the press releases would be a Post and categorized as a press release. Or you may want to create a post type for landing pages. It may very well belong as a custom type, but it likely could also exist as a Page.

For the sake of this article, we’re going to follow a real-world scenario of creating a Project post type to store samples of work. We’ll register the post type, add some meta data to it, include additional information in WordPress’ administration screens, and create custom taxonomies to supplement.

Registering The Post Type Link

To get started, we’ll need some code to register the post type. We’re going to go with an object-oriented approach because we’ll be spinning off this post type later with some added functionality that would be done much more efficiently with an object model. To start, let’s create the function that registers the post type.

function create_post_type() {
  $labels = array(
    'name'               => 'Projects',
    'singular_name'      => 'Project',
    'menu_name'          => 'Projects',
    'name_admin_bar'     => 'Project',
    'add_new'            => 'Add New',
    'add_new_item'       => 'Add New Project',
    'new_item'           => 'New Project',
    'edit_item'          => 'Edit Project',
    'view_item'          => 'View Project',
    'all_items'          => 'All Projects',
    'search_items'       => 'Search Projects',
    'parent_item_colon'  => 'Parent Project',
    'not_found'          => 'No Projects Found',
    'not_found_in_trash' => 'No Projects Found in Trash'

  $args = array(
    'labels'              => $labels,
    'public'              => true,
    'exclude_from_search' => false,
    'publicly_queryable'  => true,
    'show_ui'             => true,
    'show_in_nav_menus'   => true,
    'show_in_menu'        => true,
    'show_in_admin_bar'   => true,
    'menu_position'       => 5,
    'menu_icon'           => 'dashicons-admin-appearance',
    'capability_type'     => 'post',
    'hierarchical'        => false,
    'supports'            => array( 'title', 'editor', 'author', 'thumbnail', 'excerpt', 'comments' ),
    'has_archive'         => true,
    'rewrite'             => array( 'slug' => 'projects' ),
    'query_var'           => true

  register_post_type( 'sm_project', $args );

Not much mysterious here. We’re calling the create_post_type function, which registers our post type. We’re giving the type some labels for back-end identification and giving it a list of specifications in what it can do. For a full list of reference for each of these variables, take a look at the WordPress Codex6, but we’ll hit on a few key items.

labels Link

For brevity, we’ve created a labels array and simply passed it to the arguments array. WordPress enables us to identify a slew of labels for singular, plural and other purposes.

public Link

This setting is a parent of sorts for a few of the other settings that appear later in the list. The default value for the public attribute is false. The value of public is passed to the following other attributes when they are not explicitly defined: exclude_from_search, publicly_queryable, show_in_nav_menus and show_ui.

The default setting here is the opposite of the public attribute. If your post type is public, then it will be included in the website’s search results. Note that this has no implication for SEO and only restricts or allows searches based on WordPress’ native search protocol. There’s a chance you would want the post type to be public but not appear in these search results. If that’s the case, set this attribute to true.

publicly_queryable Link

This attribute is exclusively for front-end queries and has no real back-end implications. The default value is the same as the public attribute. Note that when it’s set to false, you will not be able to view or preview the post type in the front end. For example, if you wanted to create a post type that populates a personnel page with a list of everyone’s name, title and bio but didn’t want them to have their own URL on the website, then you would set publicly_queryable to false.

show_ui Link

Most of the time, you’ll want to set show_ui to true. The default value pulls from the public attribute but can be overridden. When it’s set to false, then a UI element on WordPress’ administration screen won’t be available to you. A practical reason why you would want this set to false is if you had a post type that merely managed data. For example, you may want an Events post type that has a recurring attribute. When you save an event, new posts of a different type would be created to handle each event occurrence. You would want the UI to show only the primary Events post type and not the event occurrence’s meta data.

show_in_nav_menus Link

Pretty simple and straightforward. If you don’t want this post type to appear in WordPress’ default menu functionality, set this to false. It takes the value of public as default.

show_in_menu Link

You can modify the position of the post type in the back end. When set to true, the post type defaults as a top-level menu (on the same hierarchical level as Posts and Pages). If false, it won’t show at all. You can use a string value here to explicitly nest the post type into a top level’s submenu. The type of string you would provide is tools.php, which would place the post type as a nested element under “Tools.” It derives its default value from show_ui. Note that show_ui must be set to true in order for you to be able to control this attribute.

show_in_admin_bar Link

Pretty self-explanatory. If you want UI elements added to WordPress’ administration bar, set this to true.

The default value of null will place the menu (at the top level and if not overridden using show_in_menu) below “Comments” in the back end. You can control this further by specifying an integer value corresponding to WordPress’ default menu placements. A value of 5 will place the post type under “Posts,” 10 under “Media,” 15 under “Links,” and 20 under “Pages.” For a full list of values, check out the WordPress Codex7.

You can pass a URL to this attribute, but you could also simply use the name of an icon from Dashicons for a quick solution. Supplying the attribute dashicons-admin-appearance would give you a paint brush8. A full list of Dashicons is available9 as a handy resource. The default is the thumbtack icon used for Posts.

capability_type Link

This attribute quickly gets into some advanced user-role segmenting concepts. Essentially, assigning post to this attribute generates a capability structure that exactly mimics how access to Posts works. Using this value, subscribers would not be able to access this post type, whereas Authors, Editors and Administrators would. Using page here would limit access to just Editors and Administrators. You can define a more granular structure using capability_type and capabilities attributes in the arguments list. Note that we did not use the capabilities attribute in this example because we’re not explicitly defining a custom capability structure to be used with this post type. This is an advanced concept and one for a completely different article.

hierarchical Link

This is basically the difference between a Post and a Page. When set to true, a parent post can be identified on a per-post basis (basically, Pages). When false, it behaves as a Post.

supports Link

A whole bunch of default functionality is attached to each new post type. This array tells WordPress which one of those to include by default. There may be an instance when you don’t want the editor on your post type. Removing that from the array will remove the editor box on the post’s editing screen. Eligible items for the array include the following:

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

has_archive Link

When this is set to true, WordPress creates a hierarchical structure for the post type. So, accessing /projects/ would give us the standard archive.php view of the data. You can template out a variant of archive.php for this particular archive by creating a new file in your theme system named archive-sm_project.php. You can control the default behavior at a more granular level by spinning it off from your primary archive.php.

rewrite Link

The rewrite option allows you to form a URL structure for the post type. In this instance, our URL would be{slug}/, where the slug is the portion assigned by each post when it’s created (normally, based on the title of the post). A second variable can be assigned inside the rewrite array. If you add with_front => false (it defaults to true), it will not use the identified front half of the URL, which is set in “Settings” → “Permalinks.” For example, if your default WordPress permalink structure is /blog/%postname%/, then your custom post type would automatically be /blog/projects/%postname%/. That’s not a good outcome, so set with_front to false.

query_var Link

This attribute controls where you can use a PHP query variable to retrieve the post type. The default is true and renders with the permalink structure (when set). You can use a string instead of a variable and control the key portion of the query variable with the string’s value.

Extending The Post Type With A Taxonomy (Or Two) Link

Out of the box, WordPress Posts have categories and tags attached to them that enable you to appropriately place content in these buckets. By default, new post types don’t have any taxonomies attached to them. You may not want to categorize or tag your post type, but if you do, you’d need to register some new ones. There are two variants of taxonomies, one that behaves like categories (the checklist to the right of the posts) and one like tags, which have no hierarchical structure. They behave in the back end pretty much in the same way (the only discernable difference being that categories can have children, whereas tags cannot), but how they’re presented on the administration screen varies quite wildly. We’ll register two taxonomies to give us one of each type.

function create_taxonomies() {

  // Add a taxonomy like categories
  $labels = array(
    'name'              => 'Types',
    'singular_name'     => 'Type',
    'search_items'      => 'Search Types',
    'all_items'         => 'All Types',
    'parent_item'       => 'Parent Type',
    'parent_item_colon' => 'Parent Type:',
    'edit_item'         => 'Edit Type',
    'update_item'       => 'Update Type',
    'add_new_item'      => 'Add New Type',
    'new_item_name'     => 'New Type Name',
    'menu_name'         => 'Types',

  $args = array(
    'hierarchical'      => true,
    'labels'            => $labels,
    'show_ui'           => true,
    'show_admin_column' => true,
    'query_var'         => true,
    'rewrite'           => array( 'slug' => 'type' ),


  // Add a taxonomy like tags
  $labels = array(
    'name'                       => 'Attributes',
    'singular_name'              => 'Attribute',
    'search_items'               => 'Attributes',
    'popular_items'              => 'Popular Attributes',
    'all_items'                  => 'All Attributes',
    'parent_item'                => null,
    'parent_item_colon'          => null,
    'edit_item'                  => 'Edit Attribute',
    'update_item'                => 'Update Attribute',
    'add_new_item'               => 'Add New Attribute',
    'new_item_name'              => 'New Attribute Name',
    'separate_items_with_commas' => 'Separate Attributes with commas',
    'add_or_remove_items'        => 'Add or remove Attributes',
    'choose_from_most_used'      => 'Choose from most used Attributes',
    'not_found'                  => 'No Attributes found',
    'menu_name'                  => 'Attributes',

  $args = array(
    'hierarchical'          => false,
    'labels'                => $labels,
    'show_ui'               => true,
    'show_admin_column'     => true,
    'update_count_callback' => '_update_post_term_count',
    'query_var'             => true,
    'rewrite'               => array( 'slug' => 'attribute' ),

When registering a post type, a new menu will appear in the dashboard.10
After you register the new post type and taxonomies, you’ll be greeted with a handy menu in the back end. (View large version11)

All right, now we have two new taxonomies attached to the new post type. The register_taxonomy function takes three arguments. The first is the taxonomy’s name, the second is an array or string of post types, and the third is the arguments defined above.

A quick note on our prefixing. Our post type and taxonomies are all prefixed with sm_. This is by design. We don’t want future plugins to interrupt our infrastructure, so we simply prefix. The name of the prefix is completely up to you.

So, we’ve got a new post type and two new taxonomies attached to it. This essentially replicates the default Posts behavior of WordPress. This is all good stuff, but let’s dig a little deeper to make it more integrated.

Enhancing The Experience With Meta Data Link

Creating additional fields available to the author in WordPress’ administration screen can be a bit tricky — but abundantly useful. Where WordPress underperforms its competitors is precisely in this area. There’s no user interface where you can define additional pieces of information on a per-post basis. Make no mistake, WordPress fully supports this behavior, but it’s more of a developer tool than an out-of-the-box tool, which makes sense. One might need an endless number of combinations of additional fields. Even if WordPress provided a slick back-end interface to allow a non-technical user to define these fields, there’s no real seamless way to display that information in the front end without a developer putting their hands on it and making it so.

This is where Advanced Custom Fields12 comes in. ACF is a wonderful plugin that gives developers this interface and a full array of templating functions to pull the data in the front end. This article doesn’t detail how to do that, but ACF gives ample documentation13 to get you started and working in the ACF environment.

A preview of the ACF UI for creating custom metadata.14
ACF makes creating meta data and conditionally attaching to custom post types a snap. (View large version15)

Using ACF, you can define new fields and conditionally attach them to content throughout the website. For example, we could create a timeframe meta field that collects how long a particular project has taken. We could add additional fields for awards won or create fields to represent a list of references for any given project.

Using ACF really opens up the hood for what’s possible in WordPress.

Adding Columns To The Administration Screen Link

Viewing a list of your posts on the administration screen will give you the checkbox, the title and the date published. When registering taxonomies to the post type, you’ll get an additional column for each additional taxonomy. For the majority of cases, this is sufficient. But there may be an additional case or two where you need to provide a little more information. For example, referencing pieces of meta data in the administration grid might be useful. Maybe you want a quick reference for the timeframe or the awards field we defined above. We’ll need two functions attached to some WordPress hooks.

WordPress will give you a standard UI that will closely mirror the Post UI.16
WordPress gives you a standard administration screen out of the box that closely mirrors the built-in Post post type. (View large version17)

Let’s look at the code:

function columns($columns) {
  return array_merge(
      'sm_awards' => 'Awards',
      'sm_timeframe' => 'Timeframe'

The first line unsets the date column. You can unset any of the default columns that you wish. The second line unsets the custom taxonomy we registered (the tag-like one, not category). This could be useful for keeping the admin screen neat and tidy. As you may have noticed, we also unset the comments and author — information we didn’t think was necessary on the screen.

Then, we’re simply defining the new columns and merging them with the array that was passed in the function. We created two new columns, one for awards and one for timeline. The array keys are completely arbitrary. They could be anything, but we’ll need to reference them again when it comes time to pull data into those columns… which is what we’re going to do next.

function column_data($column,$post_id) {
  switch($column) {
  case 'sm_awards' :
    echo get_post_meta($post_id,'awards',1);
  case 'sm_timeframe' :
    echo get_post_meta($post_id,'timeframe',1);

All right, we’ve fetched the meta data and conditionally outputted it based on what column we’re on. This is where we’re referencing the array key from above. So, as long as they’re both the same, we could use any arbitrary string we want. Note that we’re pulling the meta fields over using WordPress’ native get_post_meta function.

Removing a few built-in columns and adding some of our own enhances the UI.18
Removing some built-in columns and adding a few of our own enhances the UI and streamlines the information displayed. (View large version19)

Sorting Link

Ah, sorting. As you probably know, WordPress sorts Pages by menu order and then alphabetically by title and Posts by date published. Let’s get fancy and sort our new post type by the number of awards won. The use case here is easy to see. You want your most award-winning work at the top of the list at all times. If we use standard WordPress queries, the order we’re about to establish will be honored – universally across the website. We will need a function to join the wp_posts and wp_postmeta tables and another to revise how the data is sorted.

function join($wp_join) {
  global $wpdb;
  if(get_query_var('post_type') == 'sm_project') {
    $wp_join .= " LEFT JOIN (
      SELECT post_id, meta_value as awards
      FROM $wpdb->postmeta
      WHERE meta_key =  'awards' ) AS meta
      ON $wpdb->posts.ID = meta.post_id ";
  return ($wp_join);

This function does the joining for us. We won’t get into why that select statement works (that’s for another article altogether). Pay attention to the if statement here. We’re determining the post type and then conditionally running the join if it meets the sm_project condition. Absent this if statement, you would be doing this join regardless of type, which is not likely something you want.

There could also be a case where you just want to sort the administration screens and not the front end. Fortunately, we can use WordPress’ built-in conditional statements to do that job. Just wrap your statement with another conditional and check against is_admin.

function set_default_sort($orderby,&$query) {
  global $wpdb;
  if(get_query_var('post_type') == 'sm_project') {
    return "meta.awards DESC";
  return $orderby;

Once again, we’re verifying our post type and then returning an amended order statement. Now we’re telling WordPress to order by the value from the wp_postmeta table descending. So, we’ll get a list of our awards from the most won per project to the least won per project.

Putting It All Together Link

None of these functions will do anything until they’re called and attached to WordPress hooks. We’ll do this and keep it neat by creating an object around the post type and using the constructor to attach each function to the appropriate hook. For brevity, we’re not going to repeat the code already referenced above.

class sm_project {

  function __construct() {

  function create_post_type() {

  function create_taxonomies() {

  function columns($columns) {

  function column_data($column,$post_id) {

  function join($wp_join) {

  function set_default_sort($orderby,&$query) {

new sm_project();

Voila! Everything has come together quite nicely. In our constructor, we referenced the appropriate actions and filters. We’re performing these functions in a particular order — and this must be followed. The post type has to be created first, the taxonomies attached second, then any sort of custom sorting. Keep that in mind as you’re creating your data type.

Summing Up Link

Once you get the hang of it and you create a few of these, it’ll start to come naturally. I’ve got a bunch of these clips saved up in my toolbelt. I rarely create these from scratch anymore. Although this article is long and in-depth, it really is about a 10-minute process from concept to conclusion once you fully understand what’s going on.

References Link

(dp, al)

Footnotes Link

  1. 1
  2. 2
  3. 3
  4. 4
  5. 5
  6. 6
  7. 7
  8. 8
  9. 9
  10. 10
  11. 11
  12. 12
  13. 13
  14. 14
  15. 15
  16. 16
  17. 17
  18. 18
  19. 19
  20. 20
  21. 21
  22. 22
  23. 23
  24. 24
  25. 25
  26. 26
  27. 27

↑ Back to top Tweet itShare on Facebook

Brian Onorio is the Founder and President of Walk West (Raleigh, North Carolina), a full service digital agency specializing in Web Design and Internet Marketing. He earned his Bachelor of Science in Computer Science from North Carolina State University and brings 10 years of industry experience to the table. He plays an active role in day-to-day project operations from strategy to release.

  1. 1

    Mark Howells-Mead

    April 22, 2015 2:40 pm

    Is the technique of using a constructor function with the same name as the class a norm, or an alternative to __construct ?

    • 2

      Brian Onorio

      April 22, 2015 3:08 pm

      Mark, you’re correct. It’s a different syntax for older PHP installs we intentionally used to ensure compatibility across the board. Both will work. Your example was introduced in PHP5.

      • 3

        Nicolae Vartolomei

        April 22, 2015 8:54 pm

        You definitely should change this to __constructor(), because PHP 4 is deprecated ages ago and because this type of constructors are being removed in the next major PHP release.

        WordPress doesn’t even work on PHP 4.

        • 4

          Brian Onorio

          April 23, 2015 1:17 pm

          Old habits die hard. You all are completely correct. We’ve updated the article to use the __construct() syntax for PHP5.

  2. 5

    Make sure you add the code in a separate plugin and not in you theme functions.php file. This ensures that you don’t lose your custom post types when you switch your theme.

    • 6

      Brian Onorio

      April 22, 2015 3:10 pm

      Good point. We didn’t go through creating a plugin around the post type, but you could certainly do that to retain functionality across themes.

    • 7

      The other correct option would be to create a child theme ;)

      cu, w0lf.

    • 8

      Correct me if I’m wrong, but if you go the plugin route, don’t you also lose the custom post types if you turn off the plugin? True, you don’t want to put this in the themes functions.php, the solution I’m working on right now uses a child theme.

      • 9

        Rob Golbeck

        June 4, 2015 3:52 pm

        Yes, you’ll lose the custom post type if you turn off the plugin, but you’ll also lose it if you change the child theme. A good way to prevent either scenario is by putting it in a ‘mu-plugins’ folder (must-use plugins). Must-use plugins are always on, and can’t be inadvertently deactivated or deleted from the admin. (See:

  3. 10

    When creating the custom post type, how would you make it show up as a child to an existing admin menu item? For example, put ‘Projects’ under ‘Media’.

    • 11

      Brian Onorio

      April 22, 2015 3:10 pm

      Tania, check the show_in_menu argument for the register_post_type function. You have a wide array of options in where to place the CPT.

  4. 13

    I was just wondering if anyone have experience with using for custom fields in comparison with ACF mentioned in the article.

    • 14

      Brian Onorio

      April 22, 2015 3:48 pm

      I’ve seen Pods, but have never experimented with it. I can definitely vouch for ACF as we use it extensively in our in-house built WP framework.

    • 15

      Pods does not come with “repeatable fields”, yet. But hopefully in next big release.

  5. 16

    I’ve been using Types plugin for a last few years to create custom types without having to write any code. All the args (public, show_ui etc.) can be changed via a GUI.

    • 17

      Brian Onorio

      April 22, 2015 4:18 pm

      There are indeed quite a few plugins to accomplish this. However, with something that is as simple as registering post types, we’d rather not be dependent on a plugin for future maintainability. When you get down in the code, your options are limitless. For example, the few functions we’ve added to customize ordering and positioning is something not often found through a GUI as the functionality is so specific to individual needs.

  6. 18

    Charles Conner

    April 22, 2015 4:43 pm

    Very weird – I tried to add the code (piece by piece, then entire source) to my functions.php file, and it’s throwing up a white screen of death. I realize the practical way of doing this is by adding a plugin, but shouldn’t it work in my functions file?

    • 19

      Hi Charles,

      Yep, it should! I recommend going into wp-config.php in the root WordPress directory and setting WP_DEBUG to true which will allow you to see the error messages :)


  7. 20

    Debasish Panda

    April 22, 2015 6:00 pm

    Yes, I regularly build sites using Custom Post Types & Custom Taxonomies. Sometimes they will have a simple CPT and few entries in them, and some sites have have 4/5 post types, each having a several thousand items.

    One interesting thing I have observed that with having several thousand pages a WP site gets visibly slow to load, atleast in the backend atleast, particularly if they are in 2 to 3 level hierarchy [think of a Directory structure], but you can definitely build a similar directory structure using CPT & taxonomies, the site will remain fast as usual.

    • 21

      Ihor Vorotnov

      June 7, 2015 12:10 am

      That’s one of the very old WordPress not so good “features” – it always fetches all pages / hierarchical post types in the admin. It’s mentioned on register_post_type() Codex page.

  8. 22

    Surajit Kayal

    April 22, 2015 6:03 pm

    Why not use a great plugin like: “More Types” : It will eliminate you job of registering the post, and the rest can be done quite easily!

    • 23

      “more types” is abandonware and throws errors if you are trying to update up out of it. If anyone has any sharp ideas about Undefined index: (the content type) in /wp-content/plugins/more-types/more-types-object.php on line 51

  9. 24

    Nice read Brian.

    When creating a cpt do you need to specify it in a loop for the post to display or does it just use the generic loop if you have set its hierarchical to true?

    Another thumbs up for ACF.

    • 25

      Bjanrni, the WordPress standard loops will be used according to the WordPress hierarchy. If no CPT specific files exist, it’ll use archive.php (or index.php if archive.php doesn’t exist) and single.php to render the output for the archive list and singular post respectively.

      For templating purposes, you may create archive-your_post_type.php to render an archive specifically for the CPT and single-your_post_type.php for the singular posts.

  10. 26

    OR we just install the TYPES plugin

  11. 27

    Thanks Brian. This article was very timely as I’ve just started implementing custom post types and taxonomies in to themes. This really helped conceptually pull a few things together for me.

  12. 28

    Great post, almost all about custom post types put it together. I just miss the output when you need to go through the archive page and I will definitely add some information about filtering. I´m personally using Search&Find from design and code to allow my visitors sort my Custom post type information.

  13. 29

    This is such a wonderful post… though I’m already using custom post types on one of my sites these days.

  14. 30

    Beginner question, but in which file does this code go, to create a custom post type?

  15. 32

    Great article! I am using CPTs since about a year now and had to learn some things the hard way. Right now I am working on a new site, and I was wondering if there’s any quantity limit when it comes to CPTs and WordPress? Right now we’re planning to create more than 30 custom post types, and we’re not sure if wordpress is able to handle that many types, also performance wise. I read a few posts and comments, but I wasn’t able to find any official docs or answers on that topic.

    • 33

      Brian Onorio

      April 24, 2015 9:23 pm

      Genesis, I’m not sure if there’s a hard cap on these. I don’t think there is outside of physical limits like disk space and DB size.

      That all being said, be sure you’re using post types correctly. As I mentioned in the article, there may very well be a case for a CPT around press releases. 90% of the time, that should be a categorized post as opposed to branched out.

      One that that CPTs give you is granular control over access roles. If a certain segment needs access to only one of those and there are 30 such segments, you may have a case for 30 CPTs.

      That all being said, I can’t think of a use case where 30 would be necessary – and if they truly are, you may want to look at WordPress as a whole and determine if that is indeed the correct content management system for you.

      • 34

        Ihor Vorotnov

        June 7, 2015 12:22 am

        WordPress will easily handle any amount of CPTs and CTs. As you might know, it already comes with a bunch of them – posts, pages, revisions, menus etc. Right now there’s a discussion and ongoing progress in Trac to implement widgets as CPT instead of options. Basically, all CPTs are stored in wp_posts table and have their slug in post_type column, just like posts, pages etc. As long as you have an index on that column you are safe to use any amount of CPTs. I had a use case for 28 of them (plus those built-in) – works fine.

    • 35

      If those 30 different post types are actually just simple variations of the default “post” post type, I’d suggest taking a look at Post Formats instead ;)

      cu, w0lf.

      • 36

        Ihor Vorotnov

        June 7, 2015 12:35 am

        1. You can’t register new custom post format and those built-in are pretty much the same.
        2. Post formats are just different types of post, they sit in blog url structure just like regular post (which they actually are).
        3. Custom post types are used for completely different tasks. As an example – I run a platform for sports events management and it’s powered by WordPress of course. It has CPTs – event, organization, person, location, club, venue, activity, ad and a dozen more. All of them have their own url structure, archives, taxonomies and many-to-many connections between them. Can you do this with post formats? :)
        CPTs and post formats are completely different beasts.

  16. 37

    Thank you for this clear hands-on approach! Helped me a lot!

  17. 38

    This came right in time! Thank you for sharing this.
    Maybe you could bring another article about creating a plugin with costume post types and custom archive.php that integrates in as many themes as possible. I know that’s selfish… ;-) I tried to do that but could not find a way to make it work with different themes.

    • 39

      Brian Onorio

      April 27, 2015 6:27 pm

      This is largely a developer column due to that reason alone. Unless you’re comfortable using your theme’s default archive, you’re going to have to play with the output to get the results you want. We could turn this into a plugin, but you’ll still battle with the theming – which exists (and should) largely outside the realm of the plugin itself. If you’re devoted to keeping layers of technology abstract, plugins should (largely) center on function while themes should (largely) center on aesthetic. There are of course cross overs, but that’s a general rule of thumb.

  18. 40

    Great Article! I was having hard times learning drupal but this post give me a reason to back to the old and loved wordpress. :D

    • 41

      TBH, Drupal handles this very well. However, WordPress does give developers a robust toolkit to manage these custom content types. I’ve grown to love it.

  19. 42

    Hey, Hi there. Thank you for sharing such an informative post on custom content Types in WordPress. I found it very helpful because I am new to WordPress website develpoment and your post answered a lot of my questions in best way.

  20. 44

    HI. Thanks for sharing.

    I’m a regular reader here. It’s really informative.

  21. 45

    I believe there is an error in the code line that registers the second taxonomy.

    register_taxonomy(‘sm_project_attribute’, ‘sm_project’, $args);

    Should be
    register_taxonomy(‘sm_project_attribute’, array(‘sm_project’), $args);


  22. 46

    Daniel Keith

    June 19, 2015 7:45 am

    Hi Brian,
    Great post indeed. So well written and explained.
    Customizing a WordPress website is always a charm for me.

    Thanks for sharing your professional experience.

  23. 47

    Hi Brain

    New to this in WP so very informative post. One question; how would you output the custom meta data on the front-end? How would I display the number of awards, for instance, to the user?

    Thanks, Mark

  24. 48

    Brett Powell

    July 20, 2015 5:11 pm

    Thanks for this Brian, you’ve saved the day for me, and no doubt for countless others. Its not that this information isn’t available elsewhere that makes it so valuable, its that you’ve presented it in a way that makes it easy (and enjoyable) for a beginner like me to grasp and implement. That takes real expertise, so again, thank you for your time and effort.

  25. 49

    Are you talking of custom content type or custom post type. Which one?

    I believe the two are very different concepts. So please clarify and if different, do justice to the title of this post.

    On the other hand if you have a link to a post on how to do content type negotiation with WordPress do help a brother as I’m specifically looking for a way to return json from an Ajax call to wordpress.


  26. 50

    Hi Brian! Thanks for sharing the information. It’s been very useful for me :).

    I’ve tried it, and everything works fine in the administrator area. However, If I go to or, I get a 404 error. Am I missing something?

    For the next step, I will create the archive-projects.php and single-project.php files, but I think it’s only necessary if I want to customize the appearance of the response. That’s not really necessary to display the information in the front end, is it?

    Thanks again, this is a good article!

  27. 51

    Hi Brian! Thanks for sharing the information. It’s been very useful for me :).

    I’ve tried it, and everything works fine in the administrator area. However, If I go to or, I get a 404 error. Am I missing something?

    For the next step, I will create the archive-projects.php and single-project.php files, but I think it’s only necessary if I want to customize the appearance of the response. That’s not really necessary to display the information in the front end, is it?

  28. 52

    I thought what I was looking for was quite simple but it seems the entire internet is focussed on either doing it “the other way” or saying “Install a plugin to do it for you” but nobody has been able to answer my “simple” question for me… i am really hoping you can…

    I do NOT want to do this via a plugin, I want to write the actual code to show a meta box underneath the main text area, not on the side of the screen. I have placed meta boxes on the side of the screen since day one but till this very day I have no idea how to show it in the main view.

    To explain what I want to do a little better, I want to create a tabbed area where one tab would allow you to set some graphics, another tab will list all of the posts of another custom post type and allow you to select posts from check boxes and store the chosen post id’s, another tab has some general purpose settings and drop down boxes etc etc etc. There is a LOT of data I want to capture for my custom post type and putting all of it on the side of the screen is just unpractical and stupid…

    I look at something like WooCommerce and I see it has meta data in the main area and I ask myself “How do I do that? What am I missing?”

    Please, guys, can someone please help me out and tell me how I can do this?

    Another thing I wanted to do was to make my custom post types sort of like hidden… I don’t want someone to pull an rss feed of my post types or just type my post type into the url and see whatever is in my database. I want to be able to use my custom post types to configure stuff in the dashboard then write my own plugins to show it on the front end as I see fit… But I definitely don’t want anyone to just type a few extra characters into my site url and see everything I have under my custom post types…

    If you guys could help me with either one of those two issues I would be soo super grateful!

    Thanks in advance


↑ Back to top