Using Custom Post Types

A WordPress website can contain multiple content types, known as “post types” in the WordPress world. The default post types are “Post” and “Page”, but you can create your own custom post types.

On my website I’ve created a Code Snippets post type and a Projects post type. This simplifies the content creation process by grouping different types of content in the backend, and lets you have dynamic archives. I can customize the edit screen with Custom Metaboxes and Taxonomies.

Registering a post type

In your core functionality plugin, use the register_post_type() function to create a post type.

* Register the Testimonial post type
function be_register_testimonial_post_type() {
$labels = array(
'name' => 'Testimonials',
'singular_name' => 'Testimonial',
'add_new' => 'Add New',
'add_new_item' => 'Add New Testimonial',
'edit_item' => 'Edit Testimonial',
'new_item' => 'New Testimonial',
'view_item' => 'View Testimonial',
'search_items' => 'Search Testimonials',
'not_found' => 'No Testimonials found',
'not_found_in_trash' => 'No Testimonials found in Trash',
'parent_item_colon' => 'Parent Testimonial:',
'menu_name' => 'Testimonials',
$args = array(
'labels' => $labels,
'hierarchical' => false,
'supports' => array( 'title', 'editor', 'thumbnail' ),
'public' => true,
'show_ui' => true,
'show_in_menu' => true,
'show_in_nav_menus' => true,
'show_in_rest' => true,
'publicly_queryable' => true,
'exclude_from_search' => false,
'has_archive' => true,
'query_var' => true,
'can_export' => true,
'rewrite' => array( 'slug' => 'testimonials', 'with_front' => false ),
'menu_icon' => 'dashicons-groups', //
register_post_type( 'testimonial', $args );
add_action( 'init', 'be_register_testimonial_post_type' );
view raw post-type.php hosted with ❤ by GitHub

The labels parameter lets you customize all the labels used throughout the WordPress backend. It’s usually as simple as find/replacing the plural and singular forms of the post type, but you may want to tweak some of the other labels more extensively.


The supports parameter lets you specify which WordPress features should be enabled for this post type. Options:

  • ‘title’
  • ‘editor’ (content)
  • ‘author’
  • ‘thumbnail’ (featured image, current theme must also support post-thumbnails)
  • ‘excerpt’
  • ‘trackbacks’
  • ‘custom-fields’
  • ‘comments’ (also will see comment count balloon on edit screen)
  • ‘revisions’ (will store revisions)
  • ‘page-attributes’ (menu order, hierarchical must be true to show Parent option)
  • ‘post-formats’ add post formats, see Post Formats
Show in REST

The show_in_rest parameter lets you decide if this post type should be visible in the REST API. This must be set to true if you’d like to use the new Gutenberg block editor

Has Archive

The has_archive parameter enables the post type archive, a dynamic archive listing recent content in this post type.


The rewrite parameter lets you enable and customize the URL rewriting for this content type.

Menu Icon

The menu_icon parameter lets you specify a Dashicon to use as the icon in the admin area.

Using a plugin

Rather than coding it yourself, you can use Custom Post Type UI to create and manage your custom post types. The plugin lets you specify the post type slug, labels, and settings shown above.

Bill Erickson

Bill Erickson is a freelance WordPress developer and a contributing developer to the Genesis framework. For the past 14 years he has worked with attorneys, publishers, corporations, and non-profits, building custom websites tailored to their needs and goals.

Ready to upgrade your website?

I build custom WordPress websites that look great and are easy to manage.

Let's Talk

Reader Interactions

Comments are closed. Continue the conversation with me on Twitter: @billerickson


  1. Neil says

    Great post. Bill, do you think there is a speed-to-load advantage in adding your own code in custom functions versus these custom post plugins? Or is any advantage out-weighed by their ease of use?

    • Bill Erickson says

      That’s a good question, and I’ve never tested both options from a performance standpoint. I do know that once you use the Custom Post Type UI plugin to create the post type, you can remove the plugin and everything still works (it’s only used once to create it). I don’t think there’s a performance hit for using either plugin over hand-coding, but I can’t be sure.

  2. Katarsis says

    Hi, i ´am starting my own bussiness in develope wordpress site (i love this) and i have a question
    do you update plugins in the customers sites? or disable the upg´s annoucement and do not upg never? or only when strongly necessary?

    tks but i don´t know how to treat this topic in the future?

    tks again and sorry my language (i am from uruguay)

    • Bill Erickson says

      I try and minimize the use of plugins, but there are a few that I use often. Examples would be All in One SEO (for sites not built with Thesis or Genesis), Contact Form 7, and Akismet. Since I try to use well-established plugins, I don’t often update them because the updates aren’t (usually) security-based, they only provide new features. If the old plugin is working, no reason to upgrade and take a chance of it breaking.

    • Nanette says

      I tried followed your tutorial, thanks… but I am having a problem. When I pasted this code into my custom_page template I get errors. And I dont see any of the custom post.

      • Bill Erickson says

        What type of error did you get?

        I didn’t provide the complete code for displaying them on the page, so I’m not sure what code you copied.

        • Nanette says

          I guess I need the full code that I would put int customfunctions.php (in Thesis.) Can you help me out?

          I already have custom template that replaces my thesis_custom_template that I use for my home page. Is it possible to use a conditional tag that says if front_page use template “A” and then if product_page (which is my custom post type use template “B”?

  3. Kyle Jones says

    Hey Bill-

    Great post! Exactly what I needed to get over the hump for listing all members of a staff directory (see this post).

    My issue now is with sorting the member listing alphabetically by last name. Currently, it does it by latest post ID. Here’s a screenshot and this is the template file.

    If you have any ideas I’d love to hear them.


    • Bill Erickson says

      Change query_posts to this: query_posts(‘post_type=staff-directory&showposts=-1&orderby=title&order=ASC’);

      That will sort them by the title, from A-Z.

      • Kyle Jones says

        I ran across this a bit after posting, but my situation is that I don’t use the default title, I use meta fields name_last_name and name_first_name. So, I need to be able to sort by name_last_name.


        • Bill Erickson says

          Ah, then you should change query_posts to this:

  4. ptim says

    Hi Bill, thanks so much for your great resources (love core-functionality, in particular).

    Just wanted to clarify – you’ve written: “You can customize this using the archive-ebook.php template file. For single posts, use archive-ebook.php.” Should that last line read single-ebook.php?

    I’m was having trouble with the has_archives property – eventually found that I needed to flush the rewrite rules by hitting Save on the Permalink settings. Had me scratching my head – hope it helps someone 🙂

  5. Jamie says

    Dear Bill

    i’m using a custom post type of ‘portfolio’ and the genesis framework.

    the portfolio will be the home page (for a designers site so the latest work is up front)

    i created a function in the home.php to show my ‘portfolio’ custom post type, BUT, other regular posts show too?

    how can i only show the custom post types? or is this not possible with the home.php in genesis, might need to just do a page template, and set that as the home page.

    thanks in advance

    • Bill Erickson says

      home.php should really only be used for listing your blog posts. If you want portfolio items on your homepage, I recommend:
      1. Creating a page called “Home”, then go to Settings > Reading and set it as the front page.
      2. If you still want a blog, create a page called “Blog” and in Settings > Reading set it as the posts page (home.php will be used for this page)
      3. You could either create a front-page.php template file for the front page, or a page template and then specify that the homepage is using that template. In this file, build your custom query.

      • Jamie says

        Thanks for clearing that up Bill, you just saved me a few hours of my time, as i was completely on the wrong track. I get it all now.

        Really appreciate your support 🙂

  6. Chris says

    Hey Bill, thanks for this great plugin!

    Currently I have a banner image (featured image) after the Genesis header that it is pulling from for the posts in the grid. Is there a way to set a different image for the archive page teasers? I thought possibly adding max-height to 100% in CSS might do it, but something tells me I may need to set up something to crop it on upload…it’s all a bit new to me how to do this. Would appreciate any thoughts or suggestions on how to achieve this, thanks!

  7. Jason says

    Hey Bill,

    I have set up a custom post type and taxonomy for my site but when I go to the post type page and the custom taxonomy base page they are completely blank. Not a 404, just blank. I have created an archive template, archive-kellen_clients.php and I am using your template selection function but still nothing. I have re-saved the permalink structure but this didn’t help either.

    Custom Post Type Page

    Custom Taxonomy – for a category that I created

    The post itself shows up

    Here is my entry from my functions.php file.

    Any clue what I am doing wrong? I am hoping that you have seen this before. Thank you for your help and a huge thank you for all of your tutorials.

    • Bill Erickson says

      It looks like you have a php error that’s preventing the page from loading.

      In wp-config.php, add define( 'WP_DEBUG', true ); to turn on debug mode.

      Once debug mode is on, reload those pages and you should get an error message that points you in the right direction.