LearnCreate your first WordPress Custom Post Type


writes on August 7, 2012

What is it and What it isn’t

Everyone’s talking about them. I asked Chris Coyier from CSS-Tricks what excited him about WP3 and he replied custom post types:

“Think of how Tumblr works – how you can publish photos, quotes, links and whatever else. You could create those same types now with WordPress, and build themes to support and have special styles for them. This is fantastic stuff for building custom sites!”

Make sense? Well according to Jane, Chris misinterpreted their goal:

“Custom post types aren’t really meant for that use […] Custom post types are great for things that are more or less catalogued: products (in an e-commerce site), listings for a real estate site, etc. For regular content creation as described [by Chris], you can already do [that] by using custom taxonomies and/or stylesheets to make post templates.”

Now I’m going to go out on a limb here and say that Chris made a interesting suggestion. I say this for two reasons: firstly because I think the great thing about WordPress is we can use it in the way we feel it should be used. There might be a different or better way, but there is no wrong way to do something (unless it doesn’t work!). Secondly I refuse to believe that many designers would read up on something as scarily titled as ‘custom taxonomies‘.

Indeed I expect to see WordPress themes cropping up all over the place that mimic the way Tumblr themes look and work. WooThemes have already released three.

Get your Hands Dirty

Now before we dig in I want to say this – custom post types make things a lot easy for non-technical people to use the WordPress admin to enter content. However the designer/developer still needs to have a reasonable grasp of PHP and needs to be prepared to get their hands dirty (unless they opt for a plugin that does the job for you like http://wordpress.org/extend/plugins/custom-post-template/ or http://wordpress.org/extend/plugins/custom-post-type-ui/).

For our tutorial we’re going to be editing functions.php which is your theme directory. We’re simply adding to this file, so feel free to start at the top or at the bottom – just don’t change any of the code that’s already there. For the more confident of you out there you could adapt this code into a plugin.

With a few edits we are going to create a custom post type for our portfolio, and a template which will use this information. We’ll end up with a new panel in the admin menu which looks like this:

A new overview page called “My Portfolio”:

And a new place to enter our content:

Related Reading: Beginner’s Guide to PHP for WordPress – Part 1 of 3

Step 1

Here’s the first bit of code we need to add to functions.php, which I’ll review below.

add_action('init', 'portfolio_register');

function portfolio_register() {

	$labels = array(
		'name' => _x('My Portfolio', 'post type general name'),
		'singular_name' => _x('Portfolio Item', 'post type singular name'),
		'add_new' => _x('Add New', 'portfolio item'),
		'add_new_item' => __('Add New Portfolio Item'),
		'edit_item' => __('Edit Portfolio Item'),
		'new_item' => __('New Portfolio Item'),
		'view_item' => __('View Portfolio Item'),
		'search_items' => __('Search Portfolio'),
		'not_found' =>  __('Nothing found'),
		'not_found_in_trash' => __('Nothing found in Trash'),
		'parent_item_colon' => ''

	$args = array(
		'labels' => $labels,
		'public' => true,
		'publicly_queryable' => true,
		'show_ui' => true,
		'query_var' => true,
		'menu_icon' => get_stylesheet_directory_uri() . '/article16.png',
		'rewrite' => true,
		'capability_type' => 'post',
		'hierarchical' => false,
		'menu_position' => null,
		'supports' => array('title','editor','thumbnail')

	register_post_type( 'portfolio' , $args );

Anyone who’s worked with WordPress before will recognise the structure here. We’re adding an action when the WP Admin initialises to call the function portfolio_register(). In that function we create two arrays, $labels and $args, and then use register_post_type to pull it all together. In doing so we name the new custom post type ‘portfolio’ and tell it to use the arguments from $args.

The devil is in the detail, so let’s run over some of those arguments. A full list can be found at http://codex.wordpress.org/Function_Reference/register_post_type. First let’s look at $labels:

  • name this is the (probably plural) name for our new post type
  • singular_name how you’d refer to this in the singular (such as ‘Add new ****’)

You can probably work out the rest of $labels for yourself, as they simply refer to different circumstances in which the name of your custom post type would be used.

And now $args:

  • public should they be shown in the admin UI
  • show_ui should we display an admin panel for this custom post type
  • menu_icon a custom icon for the admin panel
  • capability_type WordPress will treat this as a ‘post’ for read, edit, and delete capabilities
  • hierarchical is it hierarchical, like pages
  • rewrite rewrites permalinks using the slug ‘portfolio’
  • supports which items do we want to display on the add/edit post page

That’s the first simple step, and it should be enough to see your new custom post time in the WordPress admin. Save functions.php and take a look!

Step 2

The next thing we need to do is register a taxonomy. Or, in English, create categories for this new content type.

For example, in our portfolio we want to include the names of technologies and software used to create our work. I’m going to call this taxonomy ‘Skills’, and populate it with things like HTML, CSS and jQuery.

It’s just one line of code:

register_taxonomy("Skills", array("portfolio"), array("hierarchical" => true, "label" => "Skills", "singular_label" => "Skill", "rewrite" => true));

The first item here is the taxonomy name, ‘Skills’. The second is the name of the object type we’re applying it to, in our case the custom post type ‘portfolio’ (which is an array). Finally our arguments; you can find a full list at http://codex.wordpress.org/Function_Reference/register_taxonomy, and here we’re using just three which have the same meaning as described in Step 1.

Add that line of code in and you should now see:

You can then enter new ‘skills’ just like you’d enter categories for blog posts. It looks like this:

Step 3

The third step is to add custom data fields to the add/edit post page.

For our portfolio we can add things like the year the piece was published and details on who designed, built and produced the site/design.

There’s a bit more code here, but read through it in order and it should make sense:

add_action("admin_init", "admin_init");

function admin_init(){
  add_meta_box("year_completed-meta", "Year Completed", "year_completed", "portfolio", "side", "low");
  add_meta_box("credits_meta", "Design & Build Credits", "credits_meta", "portfolio", "normal", "low");

function year_completed(){
  global $post;
  $custom = get_post_custom($post->ID);
  $year_completed = $custom["year_completed"][0];
  <input name="year_completed" value="<?php echo $year_completed; ?>" />

function credits_meta() {
  global $post;
  $custom = get_post_custom($post->ID);
  $designers = $custom["designers"][0];
  $developers = $custom["developers"][0];
  $producers = $custom["producers"][0];
  <p><label>Designed By:</label><br />
  <textarea cols="50" rows="5" name="designers"><?php echo $designers; ?></textarea></p>
  <p><label>Built By:</label><br />
  <textarea cols="50" rows="5" name="developers"><?php echo $developers; ?></textarea></p>
  <p><label>Produced By:</label><br />
  <textarea cols="50" rows="5" name="producers"><?php echo $producers; ?></textarea></p>

First of all we call the add the admin_init function to the queue when the WordPress admin initialises, and within that function we add two meta boxes – places to enter our data. The context for these two statements is

<?php add_meta_box( $id, $title, $callback, $page, $context, $priority ); ?>

The only difference between the two is where we place them on the screen. The ‘year completed’ is placed in the sidebar using ‘side’ whilst the ‘credits’ are placed in the main flow of the page using ‘normal’.

Within the two functions there is some vanilla WordPress PHP code and HTML to help define our old friend custom fields. Make sure to include

global $post;

…so that we can then query the current post using

$custom = get_post_custom($post->ID);

Once the two new meta boxes have been added it looks like this:

The final thing to do in step 3 is to make sure we then save these values with this post. I do this with

add_action('save_post', 'save_details');


function save_details(){
  global $post;

  update_post_meta($post->ID, "year_completed", $_POST["year_completed"]);
  update_post_meta($post->ID, "designers", $_POST["designers"]);
  update_post_meta($post->ID, "developers", $_POST["developers"]);
  update_post_meta($post->ID, "producers", $_POST["producers"]);

There’s nothing too tricky here. Again we’re adding an action, this time to the ‘save_post’ event. It fires the function save_details() which uses update_post_meta (http://codex.wordpress.org/Function_Reference/update_post_meta) to save the relevant data.

Step 4

A nice little touch is to rejig the layout of the My Portfolio page to display some of this information. If you’ve followed through the article so far this should all make sense to you:

add_action("manage_posts_custom_column",  "portfolio_custom_columns");
add_filter("manage_edit-portfolio_columns", "portfolio_edit_columns");

function portfolio_edit_columns($columns){
  $columns = array(
    "cb" => "<input type="checkbox" />",
    "title" => "Portfolio Title",
    "description" => "Description",
    "year" => "Year Completed",
    "skills" => "Skills",

  return $columns;
function portfolio_custom_columns($column){
  global $post;

  switch ($column) {
    case "description":
    case "year":
      $custom = get_post_custom();
      echo $custom["year_completed"][0];
    case "skills":
      echo get_the_term_list($post->ID, 'Skills', '', ', ','');

Here we’re adding two more functions to the WordPress Admin. The first, portfolio_edit_columns($columns), simply defines the columns. The first two arguments “cb” and “title” are part of the core so don’t play with those too much (you can of course rename “Portfolio Title”). It’s the next three that come from our custom post type, “description”, “year” and “skills”.

We have one more function to tell WordPress where to get this data from – portfolio_custom_columns($column). Using a simple switch/case we can define what data to actually show in the column layout. For the “description” we use the_excerpt(), for “year” we get the custom field data using get_post_custom(), and for “skills” we get a comma separated list of the terms/taxonomies/categories using get_the_term_list().

With that we have our customised columns:

One final touch

Be default custom post types will display using single.php, or index.php as a fallback. The great thing is we can create our own custom template using the filename single-xxxxxx.php. In our case this is single-portfolio.php. Just create your template file in your theme directory and the custom post type will use it. How you display the data is totally up to you.

Hey, did you also notice the ‘Featured Image’ option on the add/edit page. That works out-of-the-box thanks to the ‘thumbnail’ part of:

'supports' => array('title', 'editor', 'thumbnail')

You just need to make sure to include the following line of code in functions.php:


For more details check out this excellent post by Mark Jaquith.

Related Reading: 6 Must-Have Tools in a PHP Developer’s Toolkit

Rewrite Problems?

There are a few reported problems out there about the rewriting of custom URLs; in short, they sometimes don’t work. However, if you’re experiencing this problem there are a couple of easy fixes.

First, simply go to the Settings > Permalinks page, which will flush the permalinks (assuming that your WordPress install can write to the .htaccess file). This should clear up most problems related to permalinks, custom post type related or not.

If that doesn’t work you can add a line of code after you register the post type:

	register_post_type( 'portfolio' , $args );

This worked for me on a particularly problematic install.


And there we have it! That is a whistle-stop tour of custom post types which I hope gives you the appetite to explore further.

The best thing is to experiment with the code, read through the documentation in the WordPress Codex, and share your ideas with others. Like any new feature I’m sure it will evolve as users create new and exciting ways to use it, and we’d love to hear about how you’re using custom post types here at Treehouse.

Looking to learn more about coding, design, and more? Check out the Treehouse 7-day free trial to get started.

This article was written by Richard Shepherd.

function portfolio_register() {

$labels = array(
‘name’ => _x(‘My Portfolio’, ‘post type general name’),
‘singular_name’ => _x(‘Portfolio Item’, ‘post type singular name’),
‘add_new’ => _x(‘Add New’, ‘portfolio item’),
‘add_new_item’ => __(‘Add New Portfolio Item’),
‘edit_item’ => __(‘Edit Portfolio Item’),
‘new_item’ => __(‘New Portfolio Item’),
‘view_item’ => __(‘View Portfolio Item’),
‘search_items’ => __(‘Search Portfolio’),
‘not_found’ => __(‘Nothing found’),
‘not_found_in_trash’ => __(‘Nothing found in Trash’),
‘parent_item_colon’ => ”

$args = array(
‘labels’ => $labels,
‘public’ => true,
‘publicly_queryable’ => true,
‘show_ui’ => true,
‘query_var’ => true,
‘menu_icon’ => get_stylesheet_directory_uri() . ‘/article16.png’,
‘_builtin’ => false,
‘rewrite’ => true,
‘capability_type’ => ‘post’,
‘hierarchical’ => false,
‘menu_position’ => null,
‘supports’ => array(‘title’,’editor’,’thumbnail’)

register_post_type( ‘portfolio’ , $args );


Learning with Treehouse for only 30 minutes a day can teach you the skills needed to land the job that you've been dreaming about.

Get Started

56 Responses to “Create your first WordPress Custom Post Type”

  1. Millions of years, hot dry air and high evaporation rates
    have contributed to the high salt concentration within the Dead Sea,
    thus which makes it one of several saltiest lakes inside world.
    Regardless in the large number of positive benefits sodium provides, when used improperly, sodium and salt
    will surely be “bad on your health”. An inexpensive,
    natural sea salt method
    to rid both one’s body and its outer covering of toxins is to take a nice soak.

  2. Hi,
    Great way to explain how to make the custom post. Great share!


  3. Denis Liamkin on October 10, 2017 at 12:53 pm said:

    I noticed that when trying to delete a custom post that has a meta section, I get to an error page with as “Undefined index:” error for each of the custom fields in the meta section.

    Is there a way around this? A solution?

  4. Was implementing this solution recently and upon trying to make my first custom post, was greeted with two errors:

    “Trying to get property of non-object error using get_post_meta()”
    “Warning: Cannot modify header information – headers already sent by…”

    Both of these were solved for me by placing

    if( !is_object($post) )

    after the “global $post;” line in the “save_details()” function.

    Hope this helps someone :).

  5. Great site you have here but I was curious if you knew of any discussion boards that cover the
    same topics talked about here? I’d really like to be a part of online community where I can get feedback from
    other experienced people that share the same
    interest. If you have any suggestions, please let
    me know. Thanks a lot!

  6. You’re awesome man. Thank u so much.

  7. Thanks for the great tutorial.

    It allows people to better organize WordPress content and build admin dashboards that are more specific to the type which they are looking for.

  8. you are truly a good webmaster. The site loading pace is incredible.

    It seems that you’re doing any distinctive trick.
    In addition, The contents are masterpiece. you have performed a fantastic job
    on this matter!

  9. My developer is trying to persuade me to move
    to .net from PHP. I have always disliked the idea because of the costs.
    But he’s tryiong none the less. I’ve been using Movable-type on a number of websites for about a year and am worried about switching
    to another platform. I have heard great things about blogengine.net.

    Is there a way I can transfer all my wordpress content into it?
    Any help would be really appreciated!

  10. Hello, I want to subscribe for this weblog to obtain latest updates, therefore where can i
    do itt please help out.

  11. Excellent tutorial. Thank you for putting it together.
    In Step 3:
    If you have debug = true, you will receive an Notice for Undefined Field.

    To fix change:
    $designers = $custom[“designers”][0];

    $designers = isset( $custom[“designers”][0] ) ? $custom[“designers”][0] : “default value”;


  12. Although a newbie, i find this easy to follow! just want to say thanks for the step by step approach =)

  13. andrew on May 26, 2016 at 11:45 am said:

    Thanks for the great tutorial. I was able to make a custom field that shows up great on the admin (back-end page editor) However I’m having a difficult time displaying the custom fields on my page template. I’ve tried get_post_custom() and get_post-meta() and I can seem to display the custom field I created.

    Thanks Again

  14. Thanks for this great tutorial!

  15. I’m a non-tech person but really interested in learning how to program. Wish me luck on my first programming project!

  16. I’ve been wanting to make my own site but not sure if I should just code them or buy a template.

    • Faye Bridge on April 18, 2016 at 2:40 am said:

      Hi Kimjane! With Treehouse you can learn the skills you need to code your own, giving you the flexibility to build exactly what you want. If you have any questions about our Library or courses, please let us know. 🙂

  17. Thanks for this guide. was stuck for a bit but got it to work.

  18. just add this code in functions.php ? or other ? sorry, iam beginner. i don’t understand from step 3.

  19. magnificent post, very informative. I’m wondering why thee other experts of this sector don’t
    notice this. You must continue your writing.
    I am confident, you have a huge readers’ base already!

    my webb blog – spekulasi bola

  20. Probably this is the simplest tutorial on CPT (Custom Post Types in WP). Custom fields is a good option. How to incorporate a CUSTOM FIELD in a CPT which holds multiple images of portfolio.

    I am clearing the issue- in WP CPT when you click ADD NEW CPT, a screen opens , in that a field (you may think custom field) you shall able to upload images, after upload they will be shown and their names will be stored in DB when you submit it, I mean the Cpt. What you say… whats the best way to do it?

    OCT group Kolkata

  21. Here is a video tutorial to create custom post type by creating a plugin, a very basic type of custom post type. No 3rd party plugin required to create it.


    This is for beginners

  22. Tried it on my other site to see if it was a problem with Dynamik and both the front and back end are completely gone. Error is:

    Parse error: syntax error, unexpected ‘=’, expecting ‘)’ in /home/content/p3pnexwpnas04_data03/67/2316567/html/wp-content/themes/Divi/epanel/custom_functions.php on line 8

    If I hit the back button I can get the custom-functions.php editing screen back, but when I delete the code and save it still throws the error. If it will expediate an answer, I just enrolled as a student here. Please help!!!

  23. I’m having trouble with getting the custom post to show on the admin panel. I double-checked that show_ui was true. I’m working on the Genesis framework with a Dynamik theme. I’ve been all over the internet trying various methods, but nothing is working to show what I need. I’ve been trying to get this to work for days now and I’m really concerned about falling behind with the project. Any help would be appreciated – so stressed out!

  24. And I wish I could let you know that relationship improved over time.
    In conclusion, while your Jack Russell may stop naturally obedient, the guy can pick up the necessary skills
    surprisingly well. c) bending down on the dog’s level in lieu of
    leaning within the dog to pet it.

  25. Hi friends, how is the whole thing, and what you desire to say regarding this piece of writing, in my view its really remarkable designed for me.

  26. Hello, most importantly thank you for posting on this point with awesome clarification. I Have seeking long to locate a decent article on how to create a Post in WordPress and at long last I got by answer with clear clarification .Thanks a considerable measure

  27. I’m trying this code now on the premium templates I’ve created. Hope this will work

    -> UPDATE: WORKING!!!!

  28. Shreyo Gi on September 7, 2013 at 2:36 am said:

    when code is from treehouse, it does work.
    I have a doubt now, basically, a custom post type gets stored in wp_post table. What if i want to store the data in my custom table (not in any wordpress tables) with all the functionality available? is that possible?

  29. anggagewor on September 3, 2013 at 2:36 am said:

    hi, how can i list the portofolio ?

    i mean like this

    foo foo foo

    thats for normal post,
    but how to get the portofolio post ?
    thanks for adv, sory for my bad english 🙂

  30. Hello,

    Thanks for the Great Tutorial.

    It works very good at the Backend.

    The fields are not displayed in the frontend.

  31. I did step 3 in its entirety and the meta boxes aren’t showing. What am doing wrong?

  32. ogunleye dare on July 11, 2013 at 4:46 am said:




    http:// http://www.fuoye.edu.ng

    The Federal University
    Oye-Ekiti has scheduled its 2013/2014 Post-UTME Screening tests to hold
    from Wednesday 31st July to Friday, 9th August, 2013.


  33. Simon on June 5, 2013 at 1:24 pm said:

    This doesn’t work at all with me. There are lots of errors in functions.php, causing my website to go blank. How do I fix this?..

  34. There might be a different or better way, but there is no wrong way to do something (unless it does not work!)

    It is because of programmers who think so I have to kill me to customize something in wp. How come no wrong way to do something in wp?

    Writing a waste of code that nobody can understand.

  35. Kaelin on May 24, 2013 at 2:56 pm said:

    I’ve converted the tutorial to the correct characters, and it’s working very well- except my custom meta fields are also showing up on the rest of my post editing pages. I’m assuming it has something to do with using global $post without a nonce field, but I’m having trouble implementing it. Any ideas?

    • Guest on June 6, 2013 at 2:00 pm said:

      I’m having the same problem. And posts that were created while I was working through this will have some fields displayed by the_meta(), ones done after show all of them, and ones created before don’t show them. Weird.

  36. Thank youuuuuuu soooo much! U saved my life!

  37. SHihab Malayil on May 13, 2013 at 3:37 pm said:

    < =

  38. Really helpful tutorial, thanks!

  39. MartinRheaume on April 21, 2013 at 5:58 pm said:

    This isn’t working for me. After the first step of adding the bit of code in functions.php, I’m getting a parse error. I did not edit any of the code in the file, only added the given code at the end.

Leave a Reply

You must be logged in to post a comment.

man working on his laptop

Are you ready to start learning?

Learning with Treehouse for only 30 minutes a day can teach you the skills needed to land the job that you've been dreaming about.

Start a Free Trial
woman working on her laptop