Custom Post Type


Let me get something off my chest. WordPress sometimes, just ever-so occasionally, makes things sound a little harder than they actually are. Custom post types sound quite scary, but really they aren’t. Jane Wells, WordPress UI guru, commented on their name in my last article for Think Vitamin:

“They just got named poorly because they live in the Posts table in the database. Think of them more like custom objects/content types.”

Cool – it’s just custom content. What’s the point of that then? Can’t we just categorise blog posts and order our pages in nice hierarchies? Can’t we use conditionals to spit out different CSS stylesheets? Well yes, we could. But admit it – it’s not fun.

And hands up if you’ve ever tried to explain to a client what a custom field is. Hurts, doesn’t it? So thank goodness for wonderful people like Jane who have given us a solution.

 

What is it and What it isn’t

Everyone’s talking about them. I recently 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/ orhttp://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 custom post type admin panel

A new overview page called “My Portfolio”:

Custom Post Type admin page

And a new place to enter our content:

Custom post type edit screen

Step 1

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
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 functionportfolio_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:

1
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 athttp://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:

A custom post type admin panel

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

Custom post types - add skills

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:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
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];
  ?>
  Year:
  <input type="text" name="year_completed" value="<?php echo $year_completed; ?>" />
  ID);
  $designers = $custom["designers"][0];
  $developers = $custom["developers"][0];
  $producers = $custom["producers"][0];
  ?>

Designed By:
<?php echo $designers; ?>

Built By:
<?php echo $developers; ?>

Produced By:
<?php echo $producers; ?>

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

1
<?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

1
global $post;

…so that we can then query the current post using

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

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

Custom post types - add new post

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

1
add_action('save_post', 'save_details');

and

1
2
3
4
5
6
7
8
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:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
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":
      the_excerpt();
      break;
    case "year":
      $custom = get_post_custom();
      echo $custom["year_completed"][0];
      break;
    case "skills":
      echo get_the_term_list($post->ID, 'Skills', '', ', ','');
      break;
  }
}

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 usethe_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:

Custom Post Type admin page

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 filenamesingle-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:

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

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

1
add_theme_support('post-thumbnails');

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

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:

1
2
	register_post_type( 'portfolio' , $args );
        flush_rewrite_rules();

This worked for me on a particularly problematic install.

Conclusion

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 theWordPress 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 Think Vitamin.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s