Tutorials on Custom Post Types

Understanding Custom Post Types can be quite daunting; however, these posts will help you understand, properly register, develop, and use custom post types as WordPress intended along with custom taxonomies, custom fields and meta boxes, and post formats.

Here are my Tutorial Series on Custom Post Types

  • Understanding Custom Post Types
    [display-posts category="custom-post-types" tag="cpt-series-1" posts_per_page="-1" order="ASC" orderby="date"]
  • Developing a Custom Post Type and a Custom Taxonomy in Genesis with Custom Pages
    [display-posts category="custom-post-types" tag="cpt-series-2" posts_per_page="-1" order="ASC" orderby="date"]
post

Registering Multiple Custom Post Types

Nick Croft has a great post, Custom Post Types Made Easy, on registering multiple custom post types that I have expanded for my purposes.

On almost every install of WordPress I do, I now have at least 2 custom post types: Tweets and Bookmarks.

Since I have this in a plugin, I am using a constant for my URL base:

define( 'WPSPS_URL', WP_CONTENT_URL . '/themes/my-child-theme-folder' );

First, there is the creation function. This creation function, which I call wps_create_post_types() is the only function that I ever edit, really append.

add_action( 'init' , 'wps_create_post_types' );
/**
 * Create Post Types
 *
 * @link http://codex.wordpress.org/Function_Reference/register_post_type
 *
 */
function wps_create_post_types() {
	$post_types = array(
		'Tweet' => array(
			'rewrite' => array( 'slug' => 'tweets' ),
			'menu_icon' => WPSPS_URL . '/lib/images/tweets_16x16.png',
			'description' => 'Twitter Archive (@wp_smith)',
			'supports' => array( 'title', 'editor', 'custom-fields', 'post-formats' ),
			'taxonomies' => array( 'category' , 'post_tag' , ),
		),
		'Bookmark' => array(
			'rewrite' => array( 'slug' => 'tweets' ),
			'description' => 'Bookmarks',
			'supports'    => array( 'title', 'thumbnail', ),
			'menu_icon' => WPSPS_URL .'/lib/images/bookmarks16x16.png',
			'taxonomies'  => array( 'wps_bookmark_category', 'wps_bookmark_tags' ),
		),
	);
	foreach ( $post_types as $title => $args ) {
		wps_register_post_type( $title , $args );
	}
}

So what’s going on here is that I am cycling through the post types array registering each post type with a helper function called wps_register_post_type(). Note that I am calling this main function in the init hook, which is required for custom post type registration. So every time I add a new post type I am adding the following:

'CPT Name' => array(
	'post_type' => '', // String
	'description' => '', // String
	'rewrite' => array( 'slug' => 'cpt-names' ), // Array with 'slug'
	'menu_icon' => WPSPS_URL .'/path/to/my/images/bookmarks16x16.png', // String URL
	'supports' => array( 'title', 'editor', 'excerpt', 'revisions', 'thumbnail', 'page-attributes', 'custom-fields' ), // Array of strings = features
	'taxonomies' => array( 'category', 'post_tag', ),// Array of strings = taxonomies
),

First, while I never have to do this (because this is my code and I’ve set it up how I like it), you can add a post_type entry to your array. This is the name of the registered post type. If you didn’t, the helper functions would do it for you. In this case, ‘CPT Name’ would be registered as wps_cpt_names. It converts the name to lower case and replaces the spaces with underscores. Note, my custom post type name is always my singular title, so if I want to pluralize the slug, then I add a rewrite entry to the array. On all my custom post types I add a menu_icon (call me Vain!), and since custom post types vary, I add my own support entry to the array. However, there does exist a default if you want everything. If I want my custom post type to support some Genesis functionality, I would include them here (e.g., ‘genesis-seo’, ‘genesis-layouts’, ‘genesis-simple-sidebars’).

/**
 * Registers a post type with default values which can be overridden as needed.
 *
 * @author Nick the Geek
 * @author Travis Smith
 * @link http://designsbynickthegeek.com/tutorials/custom-post-types-made-easy
 * @link http://codex.wordpress.org/Function_Reference/register_post_type
 * @link http://wpsmith.net/2012/custom-post-types/registering-multiple-custom-post-types
 *
 * @uses sanitize_title() WordPress function that formats text for use as a slug
 * @uses wp_parse_args() WordPress function that merges two arrays and parses the values to override defaults
 * @uses register_post_type() WordPress function for registering a new post type
 *
 * @param string $title title of the post type. This will be automatically converted for plural and slug use
 * @param array $args overrides the defaults
 * @param string $prefix prefix string
 */
function wps_register_post_type( $title, $args = array() ) {
	$prefix = 'wps_';
	$sanitized_title = sanitize_title( $title );
	$plural_title = isset( $args['plural_title'] ) ? $args['plural_title'] : '';
	unset( $args['plural_title'] );

	$defaults = array(
		'labels' => wps_post_type_labels( $title , $plural_title ),
		'_builtin' => false,
		'description' => $title . __( ' Custom Post Type' , 'child-translate-domain' ),
		'menu_position' => 6, // WP default: null (below comments ~ 25)
		'menu_icon' => '', // WP default: null
		'public' => true, // WP default: true
		'publicly_queryable' => true, // WP default: value of 'public'
		'show_ui' => true, // WP default: value of 'public'
		'show_in_nav_menus' => true, // WP default: value of 'public'
		'show_in_menu' => true, // WP default: null; 'show_ui' must be true
		'has_archive' => true, // WP default: false
		'capability_type' => 'post', // WP default: post; defines 'capabilities' as well
		'map_meta_cap' => false,  // WP default: false
		'hierarchical' => false,
		'taxonomies' => array(),
		'rewrite' => array( 'slug' => $sanitized_title ),
		'query_var' => true,
		'can_export' => true,
		'supports' => array( 'title' , 'editor' , 'excerpt' , 'author' , 'comments' , 'custom-fields' , 'revisions' , 'thumbnail' , 'genesis-seo' , 'genesis-layouts' , 'genesis-simple-sidebars' ),
        );

	$args = wp_parse_args( $args, $defaults );

	// Correct show_in_menu arg
	if ( false === $args['show_ui'] && true === $args['show_in_menu'] )
		$args['show_in_menu'] = false; 

	$post_type = isset( $args['post_type'] ) ? $args['post_type'] : $prefix . str_replace( '-', '_', $sanitized_title) . 's';

	register_post_type( $post_type, $args );

}

While the PHP documentation makes the function self-explanatory, let me explain in a bit more detail. First, I sanitize the title, which formats text for use as a slug. So if the title is: “This Is the Title of My Post,” the resulting sanitized title would be: “this-is-the-title-of-my-post.” This is important because case and spacing matters.

Then for the arguments, I have a list of default arguments that I use, which basically match the WordPress defaults (and an argument could be made against having them at all). However, I include everything (except capabilities, permalink_epmask, and register_meta_box_cb) so you can see everything that you need or may want.

Next, I am getting my labels from another helper function called wps_post_type_labels()

/**
 * A helper function for generating localizable labels
 *
 * @author Travis Smith
 * @uses _x() WordPress function that retrieves translated string with gettext context
 * @uses __() WordPress function that retrieves the translated string from the translate()
 *
 * @param string $singular Singular Title/Label
 * @param string $plural Plural Title/Label (optional)
 */
function wps_post_type_labels( $singular, $plural = '' ) {
    if ( $plural == '' ) $plural = $singular . 's';

    return array(
        'name' => _x( $plural, 'post type general name', 'child-translate-domain' ),
        'singular_name' => _x( $singular, 'post type singular name', 'child-translate-domain' ),
        'add_new' => __( 'Add New' , 'child-translate-domain' ),
        'add_new_item' => __( 'Add New '. $singular , 'child-translate-domain' ),
        'edit_item' => __( 'Edit '. $singular , 'child-translate-domain' ),
        'new_item' => __( 'New '. $singular , 'child-translate-domain' ),
        'view_item' => __( 'View '. $singular , 'child-translate-domain' ),
        'search_items' => __( 'Search '. $plural , 'child-translate-domain' ),
        'not_found' =>  __( 'No '. $plural .' found' , 'child-translate-domain' ),
        'not_found_in_trash' => __( 'No '. $plural .' found in Trash' , 'child-translate-domain' ),
        'parent_item_colon' => ''
    );
}

This helper labels function pluralizes my labels for me unless I specify a plural label myself, and makes all the strings compatible for localization, which if you are not making your custom post types labels able to be translated, then you need to be. Some of you may say that you never have the need; however, if you get in the practice of localizing, then when you do have the need, your code is already ready/done instead of having to re-invest the work later (which really is more work).

Now, if you want to add Genesis functionality to existing custom post types (that you may have registered via another plugin), then you may need the following.

add_action( 'init', 'wps_add_post_type_support' );
/**
 * Add Genesis custom support for registered post types
 * Add Genesis SEO/Layout support to CPTs (if not registered with them)
 *
 * @uses add_post_type_support() Registers support of certain features for a given post_type
 * @link http://codex.wordpress.org/Function_Reference/add_post_type_support
 *
 */
function wps_add_post_type_support() {
	// Uncomment below as appropriate
	//add_post_type_support( 'post_type', 'genesis-seo' );
	//add_post_type_support( 'post_type', 'genesis-layouts' );
	//add_post_type_support( 'post_type', 'genesis-simple-sidebars' );
}

To add the above to multiple post types:

add_action( 'init', 'wps_add_post_type_support' );
/**
 * Add Genesis custom support for registered post types
 * Add Genesis SEO/Layout support to CPTs (if not registered with them)
 *
 * @uses add_post_type_support() Registers support of certain features for a given post_type
 * @link http://codex.wordpress.org/Function_Reference/add_post_type_support
 *
 */
function wps_add_post_type_support() {
	$pts = array( 'post_type1', 'post_type2' );
	foreach( $pts as $pt ) {
		// Uncomment below as appropriate
		//add_post_type_support( $pt, 'genesis-seo' );
		//add_post_type_support( $pt, 'genesis-layouts' );
		//add_post_type_support( $pt, 'genesis-simple-sidebars' );
	}
}
post

Add Genesis SEO, Layouts, & Sidebars to Custom Post Types

Now, if you want to add Genesis functionality, that is, Genesis SEO, Layouts, & Sidebars, to existing custom post types (that you may have registered via another plugin), then you need the following.

add_action( 'init', 'wps_add_post_type_support' );
/**
 * Add Genesis custom support for registered post types
 * Add Genesis SEO/Layout support to CPTs (if not registered with them)
 *
 * @uses add_post_type_support() Registers support of certain features for a given post_type
 * @link http://codex.wordpress.org/Function_Reference/add_post_type_support
 *
 */
function wps_add_post_type_support() {
	// Uncomment below as appropriate
	//add_post_type_support( 'post_type', 'genesis-seo' );
	//add_post_type_support( 'post_type', 'genesis-layouts' );
	//add_post_type_support( 'post_type', 'genesis-simple-sidebars' );
}

To add the above to multiple post types:

add_action( 'init', 'wps_add_post_type_support' );
/**
 * Add Genesis custom support for registered post types
 * Add Genesis SEO/Layout support to CPTs (if not registered with them)
 *
 * @uses add_post_type_support() Registers support of certain features for a given post_type
 * @link http://codex.wordpress.org/Function_Reference/add_post_type_support
 *
 */
function wps_add_post_type_support() {
	$pts = array( 'post_type1', 'post_type2' );
	foreach( $pts as $pt ) {
		// Uncomment below as appropriate
		//add_post_type_support( $pt, 'genesis-seo' );
		//add_post_type_support( $pt, 'genesis-layouts' );
		//add_post_type_support( $pt, 'genesis-simple-sidebars' );
	}
}
post

Developing a Custom Post Type and a Custom Taxonomy in Genesis with Custom Pages, Part 5

Now, in Developing a Custom Post Type and a Custom Taxonomy in Genesis with Custom Pages, Part 1, we have the registration of the custom post type, custom taxonomy and the addition of the metaboxes. In Part 2, we discussed the Single Page Template for the custom post type. In Part 3, we discussed the Taxonomy Template for the custom taxonomy. In Part 4, we discussed the a page template for the custom post type.

Now this tutorial will discuss Creating Columns in the Edit Custom Post Page. Justin Tadlock has an excellent post on this and is a must read: Custom columns for custom post types. For those, however, who don’t like coding, there is a good plugin that will do this work for you. Pippin’s Post Type Column Editor from Code Canyon (affil link) is only $12 and worth the money, if you just lovehate coding.

Column Editor

Before, I move forward to how to do this via php and functions.php, let me highlight Pippin’s plugin (so if you care about the code skip this and the pictures). With post Type Column Editor you can easily customize the dashboard columns for all your post types. So if you wanted to add thumbnails and excerpts to the columns of posts and/or pages (if pages supports excerpts), then you can easily add it with a few clicks. This plugin gives you a really easy to use way to modify and manage the table columns for your post types. You can display post type entry titles, categories, tags, excerpts, authors, custom meta fields, thumbnails, and custom taxonomies. You can customize the columns for each built-in and custom post type separately with a straight forward drag-and-drop interface.

So when you first register a custom post type, here is what you’ll see:

Custom Post Type with No Columns

Initial Custom Post Type

Then when you use the Post Type Column Editor, you see your various options.

Post Type Column Editor Options

Post Type Column Editor Options

For a demonstration check out this Screenr:

This obviously works well with his Easy Content Types plugin which outputs PHP code for you to embed into your plugin and/or theme.

Sometimes it becomes necessary to display more than the usual Title and Date of our post types. In this case, we want to add a new column to the edit post page to allows us to easily see which axle size our disc brakes belong to. There are some great examples of how to do that but below is an example of how we can add our axle size to the page.

First we must add the column. Use the code below and add it to our wps-admin-functions.php file.

// Add Columns for Disc Brakes Edit Posts Page
add_filter( 'manage_edit-rdwd_discbrakes_columns', 'wps_discbrakes_columns' ) ;
function wps_discbrakes_columns($column) {
	$column = array(
		'cb' => '<input type="checkbox" />',
		'title' => __( 'Disc Brakes' ),
		'axlesizes' => __( 'Axle Sizes' ),
		'date' => __( 'Date' )
	);
	return $column;
}

If we save this file and upload it, we will now see a new column called Axle Sizes. This is great but there is no content in this column. Now we need to go get the content and display here. Use this code below to do just that.

// Gets the Taxonomy Terms and retunrs them on the Disc Brakes Edit Posts Page
add_action( 'manage_wps_discbrakes_posts_custom_column', 'manage_wps_discbrakes_columns', 10, 2 );
function manage_wps_discbrakes_columns( $column, $post_id ) {
	global $post;
	switch( $column ) {
		case 'axlesizes' :
			$terms = get_the_terms( $post_id, 'wps_axlesizes' );
			if ( !empty( $terms ) ) {
				$out = array();
				foreach ( $terms as $term ) {
					$out[] = sprintf( '<a href="%s">%s</a>', esc_url( add_query_arg( array( 'post_type' => $post->post_type, 'wps_axlesizes' => $term->slug ), 'edit.php' ) ), esc_html( sanitize_term_field( 'name', $term->name, $term->term_id, 'wps_axlesizes', 'display' ) ) );
				}

				echo join( ', ', $out );
			} else {
				_e( 'No Axle Sizes' );
			}               

			break;

		default :
			break;
	}
}

The code above allows us to select one of the axle sizes and it will only display the disc brakes for that size.
Custom Column for Custom Post Type

post

Developing a Custom Post Type and a Custom Taxonomy in Genesis with Custom Pages, Part 4

Now, in Developing a Custom Post Type and a Custom Taxonomy in Genesis with Custom Pages, Part 1, we have the registration of the custom post type, custom taxonomy and the addition of the metaboxes. In Part 2, we discussed the Single Page Template for the custom post type. In Part 3, we discussed the Taxonomy Template for the custom taxonomy. For good measure, let’s create a basic page template to wrap this all up. This will create a front page of sorts for the custom taxonomy and the single custom post type pages.

A. File Setup

First, create the file as page-{whatever}.php (WordPress Codex). While you can use page-{slug}.php, this can cause some unexpected issues. So naming it page-{whatever}.php forces the user to assign it via the page templates area. So in our case, it will be page-brakes.php.

B. Template Name

The first thing you must enter with any page template is the Template Name.

<?php
/*
 *Template Name: Disc Brakes Template
 */

B. Menu Move

Now, for this example, I want to use a menu system. So I will be using the secondary menu system to accomplish this, and I want this to appear below the title.

<?php
// Place the secondary navigation menu below the title
remove_action( 'genesis_after_header', 'genesis_do_subnav' );
add_action( 'genesis_after_post_title', 'genesis_do_subnav' );

// Enable the secondary navigation menu for single post type
add_filter('genesis_options', 'wps_define_genesis_setting' , 10, 2);
function wps_define_genesis_setting( $options, $setting ) {
    if( $setting == GENESIS_SETTINGS_FIELD ) {
        $options['subnav'] = 1;
    }
    return $options;
}

If you notice, I programmatically turn on the secondary menu. However, again, in Genesis > Theme Settings, site-wide, I have the secondary navigation system turned off. Now, for this page template, I have selectively turned it on.

If you use the secondary navigation for your site, simply register a new navigation system and then add it. See the previous tutorial for this information.

C. Include the Genesis Framework **VERY IMPORTANT

<?php
genesis();

So here is our finished product: page-brakes.php

<?php

/*
 *Template Name: Disc Brakes Template
 */

// Place the secondary navigation menu below the title
remove_action( 'genesis_after_header', 'genesis_do_subnav' );
add_action( 'genesis_after_post_title', 'genesis_do_subnav' );

// Enable the secondary navigation menu for single post type
add_filter('genesis_options', 'wps_define_genesis_setting' , 10, 2);
function wps_define_genesis_setting( $options, $setting ) {
    if( $setting == GENESIS_SETTINGS_FIELD ) {
        $options['subnav'] = 1;
    }
    return $options;
}

genesis();
post

Developing a Custom Post Type and a Custom Taxonomy in Genesis with Custom Pages, Part 3

In the first tutorial, Developing a Custom Post Type and a Custom Taxonomy in Genesis with Custom Pages, Part 1, we covered the following:

  1. Register your Custom Post Type
  2. Register your Custom Taxonomy
  3. Create my Metaboxes
  4. Clear Permalinks

In the previous tutorial Developing a Custom Post Type and a Custom Taxonomy in Genesis with Custom Pages, Part 2, we covered how to Create the Single Page Template for the Custom Post Type.

In this tutorial, we will walk through how to Create a Custom Taxonomy Page Template. Now the specifics will be different from custom taxonomy to custom taxonomy. This is how a taxonomy archives will appear, just like if someone clicks on a specific category or tag (via category.php). However, I will walk you through a basic setup.

A. File Setup

First, create the file as taxonomy-{taxonomy_registered_name}.php (WordPress Codex). So in our case, it will be single-wps_axlesizes.php.

B. Menu Move

Now, for this example, I want to use a menu system. So I will be using the secondary menu system to accomplish this, and I want this to appear below the title. (**This assumes that your site hasn’t done anything with Secondary Navigation.)

<?php
// Place the secondary navigation menu below the title
remove_action( 'genesis_after_header', 'genesis_do_subnav' );
add_action( 'genesis_after_post_title', 'genesis_do_subnav' );

// Enable the secondary navigation menu for single post type
add_filter('genesis_options', 'wps_define_genesis_setting' , 10, 2);
function wps_define_genesis_setting( $options, $setting ) {
    if( $setting == GENESIS_SETTINGS_FIELD ) {
        $options['subnav'] = 1;
    }
    return $options;
}

If you notice, I programmatically turn on the secondary menu. However, in Genesis > Theme Settings, sitewide, I have the secondary navigation system turned off. Now, for this page template, I have selectively turned it on.

If you already use the secondary navigation for your site, simply register a new additional navigation system and then add it. See the previous tutorial for this information.

C. Post Meta/Post Info

Now, I would like to remove the post meta and post info. (**This assumes that your site hasn’t done anything with Secondary Navigation.

<?php
// Remove the post info function
remove_action( 'genesis_before_post_content', 'genesis_post_info' );

// Remove the post meta function
remove_action( 'genesis_after_post_content', 'genesis_post_meta' );

D. Custom Loop **VERY IMPORTANT

Now, I want to create my custom display of the content via the loop. This section will dramatically change from custom taxonomy to custom taxonomy based on what type of information you’d like to display and how you’d like to display it. This would be where you would introduce the Genesis Grid Loop, if you wanted to go that route. Since this file refers to all the terms in the custom taxonomy, we have to code thinking about them all.

<?php
remove_action('genesis_loop', 'genesis_do_loop');
add_action('genesis_loop', 'wps_do_axlesizes_loop');
function wps_do_axlesizes_loop() {
	global $query_args;  // any wp_query() args

	// Get term
	$term = get_query_var( 'term' );

	$args = array(
		'tax_query' => array(
			array(
				'taxonomy' => 'wps_axlesizes',
				'field' => 'slug',
				'terms' => $term
			)
		)
	);

	genesis_custom_loop( wp_parse_args( $query_args , $args ) );
}

E. Custom Content **VERY IMPORTANT

Finally, I want to create my custom display of the content via the content. Again, this section will dramatically change from custom taxonomy to custom taxonomy based on what type of information you’d like to display and how you’d like to display it.

<?php
remove_action('genesis_post_content', 'genesis_do_post_content');
add_action('genesis_post_content', 'wps_do_axlesizes_content');
function wps_do_axlesizes_content() {
	global $post;

	$size = 'thumbnail'; // Change this to whatever add_image_size you want
	$default_attr = array(
			'class'	=> "alignleft attachment-$size",
			'alt'	=> $post->post_title,
			'title'	=> $post->post_title,
		);

	// check if the post has a Post Thumbnail assigned to it.
	if ( has_post_thumbnail() ) {
		printf( '<a href="%s" title="%s">%s</a>', get_permalink(), the_title_attribute( 'echo=0' ), genesis_get_image( array( 'size' => $size, 'attr' => $default_attr ) ) );
	}

	the_content( __( '[Read more...]' , 'genesis' ) );

	printf( '<a href="%s" title="%s">%s</a>', get_permalink(), the_title_attribute( 'echo=0' ), __( 'click for specifications' ) );

	echo '<div class="clear"></div>';
}

Now, for SEO purposes, we wanted to include the page name that will be associated with

tags.
// Add a page title (optional). This can be hard coded to pull the title from a specific page, but it is easier just to enter the page name here.

<?php
add_action( 'genesis_before_content' , 'wps_page_title' , 10 );
function wps_page_title() {
	?>
	<h1>Disc Brake Kits</h1>
	<?php
}

F. Include the Genesis Framework **VERY IMPORTANT

<?php
genesis();

The next step would be to style the taxonomy archive accordingly and as needed, which I am not going to cover here.

So our finished product is this: single-wps_axlesizes.php

<?php
/**
 * Taxonomy Template: wps_axlesizes
 *
 * This file is responsible for the display of
 * taxonomy archives for wps_axlesizes custom taxonomy.
 *
 * @author       Travis Smith <travis@wpsmith.net>
 * @copyright    Copyright (c) 2011, Travis Smith
 * @license      http://opensource.org/licenses/gpl-2.0.php GNU Public License
 *
 */

// Place the secondary navigation menu below the title
remove_action( 'genesis_after_header', 'genesis_do_subnav' );
add_action( 'genesis_after_post_title', 'genesis_do_subnav' );

// Enable the secondary navigation menu for single post type
add_filter('genesis_options', 'wps_define_genesis_setting' , 10, 2);
function wps_define_genesis_setting( $options, $setting ) {
    if( $setting == GENESIS_SETTINGS_FIELD ) {
        $options['subnav'] = 1;
    }
    return $options;
}

// Remove the post info function
remove_action( 'genesis_before_post_content', 'genesis_post_info' );

// Remove the post meta function
remove_action( 'genesis_after_post_content', 'genesis_post_meta' );

remove_action('genesis_loop', 'genesis_do_loop');
add_action('genesis_loop', 'wps_do_axlesizes_loop');
function wps_do_axlesizes_loop() {
	global $query_args;  // any wp_query() args

	// Get term
	$term = get_query_var( 'term' );

	$args = array(
		'tax_query' => array(
			array(
				'taxonomy' => 'wps_axlesizes',
				'field' => 'slug',
				'terms' => $term
			)
		)
	);

	genesis_custom_loop( wp_parse_args( $query_args , $args ) );
}

remove_action('genesis_post_content', 'genesis_do_post_content');
add_action('genesis_post_content', 'wps_do_axlesizes_content');
function wps_do_axlesizes_content() {
	global $post;

	$size = 'thumbnail'; // Change this to whatever add_image_size you want
	$default_attr = array(
			'class'	=> "alignleft attachment-$size",
			'alt'	=> $post->post_title,
			'title'	=> $post->post_title,
		);

	// check if the post has a Post Thumbnail assigned to it.
	if ( has_post_thumbnail() ) {
		printf( '<a href="%s" title="%s">%s</a>', get_permalink(), the_title_attribute( 'echo=0' ), genesis_get_image( array( 'size' => $size, 'attr' => $default_attr ) ) );
	}

	the_content( __( '[Read more...]' , 'genesis' ) );

	printf( '<a href="%s" title="%s">%s</a>', get_permalink(), the_title_attribute( 'echo=0' ), __( 'click for specifications' ) );

	echo '<div class="clear"></div>';
}

add_action( 'genesis_before_content' , 'rdwd_page_title' , 10 );
function rdwd_page_title() {
	?>
	<h1>Disc Brake Kits</h1>
	<?php
}

genesis();
post

Developing a Custom Post Type and a Custom Taxonomy in Genesis with Custom Pages, Part 1

Recently, someone contacted me to develop a custom post type setup for their website, and they were lamenting how there is nothing on the web that succinctly walks them through the process: soup to nuts. So here’s my attempt! However, I am going to assume basic knowledge of custom post types. If you haven’t read through my Understanding Custom Post Types series yet, you may want to do that.

First, I created a new php file called wps-admin-functions.php. This is where we will house our Custom Post Types, Taxonomies, and Metaboxes. We could have added this to our theme’s function.php file but this will allow us to have a separate file dedicated to our newly added features. To do this, simply create a new text document in your favorite text editor and save as wps-admin-functions.php.

1. Register your Custom Post Type

<?php
// registration code for discbrakes post type
function wps_register_discbrakes_posttype() {
	$labels = array(
		'name' => _x( 'Disc Brakes', 'post type general name' ),
		'singular_name' => _x( 'Disc Brake', 'post type singular name' ),
		'add_new' => _x( 'Add New', 'Disc Brake'),
		'add_new_item' => __( 'Add New Disc Brake '),
		'edit_item' => __( 'Edit Disc Brake '),
		'new_item' => __( 'New Disc Brake '),
		'view_item' => __( 'View Disc Brake '),
		'search_items' => __( 'Search Disc Brakes '),
		'not_found' =>  __( 'No Disc Brake found' ),
		'not_found_in_trash' => __( 'No Disc Brakes found in Trash' ),
		'parent_item_colon' => ''
	);

	$supports = array( 'title' , 'editor' , 'thumbnail' , 'excerpt' , 'revisions' );

	$post_type_args = array(
		'labels' => $labels,
		'singular_label' => __( 'Disc Brake' ),
		'public' => true,
		'show_ui' => true,
		'publicly_queryable' => true,
		'query_var' => true,
		'capability_type' => 'post',
		'has_archive' => true,
		'hierarchical' => false,
		'rewrite' => array( 'slug' => 'discbrakes' ),
		'supports' => $supports,
		'menu_position' => 5,
		'taxonomies' => array( 'wps_axlesizes' ),
		'menu_icon' => 'http://mydomain.com/wp-content/themes/lib/images/discbrakes-icon.png'
	 );
	 register_post_type( 'wps_discbrakes' , $post_type_args );
}
add_action( 'init', 'wps_register_discbrakes_posttype' );

Notice in this code that I associate my custom post type with my coming custom taxonomy: 'taxonomies' => array( 'wps_axlesizes' ),. Also, note that I am using a prefix on both my custom post type and taxonomy. The reason this is done is that there may be a naming conflict with a plugin or other code in your theme. The prefix uniquely identifies it to prevent this. It is a best practice, and make sure you do not use the wp_ or genesis_ prefixes.

2. Register your Custom Taxonomy

<?php
// registration code for AxleSizes taxonomy
function wps_register_axlesizes_tax() {
	$labels = array(
		'name' => _x( 'Axle Sizes', 'taxonomy general name' ),
		'singular_name' => _x( 'Axle Size', 'taxonomy singular name' ),
		'add_new' => _x( 'Add New Axle Size', 'Axle Size'),
		'add_new_item' => __( 'Add New Axle Size' ),
		'edit_item' => __( 'Edit Axle Size' ),
		'new_item' => __( 'New Axle Size' ),
		'view_item' => __( 'View Axle Size' ),
		'search_items' => __( 'Search Axle Sizes' ),
		'not_found' => __( 'No Axle Size found' ),
		'not_found_in_trash' => __( 'No Axle Size found in Trash' ),
	);

	$pages = array( 'wps_discbrakes' );

	$args = array(
		'labels' => $labels,
		'singular_label' => __( 'Axle Size' ),
		'public' => true,
		'show_ui' => true,
		'hierarchical' => false,
		'show_tagcloud' => false,
		'show_in_nav_menus' => true,
		'rewrite' => array('slug' => 'axle-sizes'),
	 );
	register_taxonomy( 'wps_axlesizes' , $pages , $args );
}
add_action( 'init' , 'wps_register_axlesizes_tax' );

Custom Post TypeNotice in the registration, the taxonomy is associated with my custom post type: $pages = array( 'wps_discbrakes' );.

Once these are registered, you should see your custom post type to the left under POST.

3. Create my Metaboxes
With metaboxes, you can go the long way; however, I prefer to use a class. Soon WordPress will have a metabox class, hopefully in WordPress 3.3, so this section will be rendered “obsolete” yet still usable. First, download the custom metabox code from GitHub. For a great explanation of the code, see Bill Erickson’s tutorial. Because of the tutorial, I won’t break down this section.

Once downloaded, place the metabox folder in your lib folder in the child theme folder so that: CHILD_URL . '/lib/metabox/init.php'

So the file structure is
CHILD-THEME-FOLDER

  • IMAGES FOLDER
  • LIB FOLDER
    • METABOX FOLDER
    • IMAGES FOLDER
    • wps-admin-functions.php
  • functions.php
  • style.css
  • readme.txt
<?php
// Create Metaboxes
$prefix = '_wps_';
add_filter( 'cmb_meta_boxes', 'wps_create_metaboxes' );
function wps_create_metaboxes() {
	global $prefix;

	$meta_boxes[] = array(
		'id' => 'disk-brakes-info',
		'title' => 'Disk Brakes Information',
		'pages' => array( 'wps_discbrakes' ), // post type
		'context' => 'normal',
		'priority' => 'low',
		'show_names' => true, // Show field names left of input
		'fields' => array(
			array(
				'name' => 'Instructions',
				'desc' => 'In the right column upload a featured image. Make sure this image is at least 900x360px wide. Then fill out the information below.',
				'id' => $prefix . 'title',
				'type' => 'title',
			),
			array(
				'name' => 'Part Number',
				'desc' => 'Enter the part number (e.g., XXXXXXX)',
				'id' => $prefix . 'part_number',
				'type' => 'text'
			),
			array(
				'name' => 'Rotor',
				'desc' => '',
				'id' => $prefix . 'rotor',
				'type' => 'select',
				'options' => array(
					array('name' => 'Stainless Steel', 'value' => 'stainless-steel'),
					array('name' => 'E-Coat (Black)', 'value' => 'e-coat-black'),
					array('name' => 'Option Three', 'value' => 'none')
				)
			),
			array(
				'name' => 'Caliper',
				'desc' => '',
				'id' => $prefix . 'caliper',
				'type' => 'select',
				'options' => array(
					array('name' => 'Stainless Steel', 'value' => 'stainless-steel'),
					array('name' => 'E-Coat (Black)', 'value' => 'e-coat-black'),
					array('name' => 'Option Three', 'value' => 'none')
				)
			),
			array(
				'name' => 'Mounting Bracket',
				'desc' => '',
				'id' => $prefix . 'mounting_bracket',
				'type' => 'select',
				'options' => array(
					array('name' => 'Stainless Steel', 'value' => 'stainless-steel'),
					array('name' => 'E-Coat (Black)', 'value' => 'e-coat-black'),
					array('name' => 'Option Three', 'value' => 'none')
				)
			),
			array(
				'name' => 'Weight per Axle Set',
				'desc' => 'e.g., 45 LBS.',
				'id' => $prefix . 'weight_per_axle_set',
				'type' => 'text'
			),
		),
	);

	return $meta_boxes;
}

// Initialize the metabox class
add_action( 'init', 'be_initialize_cmb_meta_boxes', 9999 );
function be_initialize_cmb_meta_boxes() {
	if ( !class_exists( 'cmb_Meta_Box' ) ) {
		require_once(CHILD_DIR . '/lib/metabox/init.php');
	}
}

This will create the metaboxes for the custom post type.
Custom Post Type Metaboxes

4. Clear Permalinks
Now in your admin area, navigate to Settings > Permalinks and click Save Changes. This will flush and clear your permalinks. This is important if you are getting strange 404 Page Not Found errors.

So our finished product is this: wps-admin-functions.php

<?php

/**
 * Admin Functions
 *
 * This file is responsible for registering the
 * custom post types, taxonomies, and metaboxes.
 *
 * @author       Travis Smith <travis@wpsmith.net>
 * @copyright    Copyright (c) 2011, Travis Smith
 * @license      http://opensource.org/licenses/gpl-2.0.php GNU Public License
 *
 */

// registration code for discbrakes post type
// registration code for discbrakes post type
function wps_register_discbrakes_posttype() {
	$labels = array(
		'name' => _x( 'Disc Brakes', 'post type general name' ),
		'singular_name' => _x( 'Disc Brake', 'post type singular name' ),
		'add_new' => _x( 'Add New', 'Disc Brake'),
		'add_new_item' => __( 'Add New Disc Brake '),
		'edit_item' => __( 'Edit Disc Brake '),
		'new_item' => __( 'New Disc Brake '),
		'view_item' => __( 'View Disc Brake '),
		'search_items' => __( 'Search Disc Brakes '),
		'not_found' =>  __( 'No Disc Brake found' ),
		'not_found_in_trash' => __( 'No Disc Brakes found in Trash' ),
		'parent_item_colon' => ''
	);

	$supports = array( 'title' , 'editor' , 'thumbnail' , 'excerpt' , 'revisions' );

	$post_type_args = array(
		'labels' => $labels,
		'singular_label' => __( 'Disc Brake' ),
		'public' => true,
		'show_ui' => true,
		'publicly_queryable' => true,
		'query_var' => true,
		'capability_type' => 'post',
		'has_archive' => true,
		'hierarchical' => false,
		'rewrite' => array( 'slug' => 'discbrakes' ),
		'supports' => $supports,
		'menu_position' => 5,
		'taxonomies' => array( 'wps_axlesizes' ),
		'menu_icon' => 'http://mydomain.com/wp-content/themes/lib/images/discbrakes-icon.png'
	 );
	 register_post_type( 'wps_discbrakes' , $post_type_args );
}
add_action( 'init', 'wps_register_discbrakes_posttype' );

// registration code for AxleSizes taxonomy
function wps_register_axlesizes_tax() {
	$labels = array(
		'name' => _x( 'Axle Sizes', 'taxonomy general name' ),
		'singular_name' => _x( 'Axle Size', 'taxonomy singular name' ),
		'add_new' => _x( 'Add New Axle Size', 'Axle Size'),
		'add_new_item' => __( 'Add New Axle Size' ),
		'edit_item' => __( 'Edit Axle Size' ),
		'new_item' => __( 'New Axle Size' ),
		'view_item' => __( 'View Axle Size' ),
		'search_items' => __( 'Search Axle Sizes' ),
		'not_found' => __( 'No Axle Size found' ),
		'not_found_in_trash' => __( 'No Axle Size found in Trash' ),
	);

	$pages = array( 'wps_discbrakes' );

	$args = array(
		'labels' => $labels,
		'singular_label' => __( 'Axle Size' ),
		'public' => true,
		'show_ui' => true,
		'hierarchical' => false,
		'show_tagcloud' => false,
		'show_in_nav_menus' => true,
		'rewrite' => array('slug' => 'axle-sizes'),
	 );
	register_taxonomy( 'wps_axlesizes' , $pages , $args );
}
add_action( 'init' , 'wps_register_axlesizes_tax' );

// Create Metaboxes
$prefix = '_wps_';
add_filter( 'cmb_meta_boxes', 'wps_create_metaboxes' );
function wps_create_metaboxes() {
	global $prefix;

	$meta_boxes[] = array(
		'id' => 'disk-brakes-info',
		'title' => 'Disk Brakes Information',
		'pages' => array( 'wps_discbrakes' ), // post type
		'context' => 'normal',
		'priority' => 'low',
		'show_names' => true, // Show field names left of input
		'fields' => array(
			array(
				'name' => 'Instructions',
				'desc' => 'In the right column upload a featured image. Make sure this image is at least 900x360px wide. Then fill out the information below.',
				'id' => $prefix . 'title',
				'type' => 'title',
			),
			array(
				'name' => 'Part Number',
				'desc' => 'Enter the part number (e.g., XXXXXXX)',
				'id' => $prefix . 'part_number',
				'type' => 'text'
			),
			array(
				'name' => 'Rotor',
				'desc' => '',
				'id' => $prefix . 'rotor',
				'type' => 'select',
				'options' => array(
					array('name' => 'Stainless Steel', 'value' => 'stainless-steel'),
					array('name' => 'E-Coat (Black)', 'value' => 'e-coat-black'),
					array('name' => 'Option Three', 'value' => 'none')
				)
			),
			array(
				'name' => 'Caliper',
				'desc' => '',
				'id' => $prefix . 'caliper',
				'type' => 'select',
				'options' => array(
					array('name' => 'Stainless Steel', 'value' => 'stainless-steel'),
					array('name' => 'E-Coat (Black)', 'value' => 'e-coat-black'),
					array('name' => 'Option Three', 'value' => 'none')
				)
			),
			array(
				'name' => 'Mounting Bracket',
				'desc' => '',
				'id' => $prefix . 'mounting_bracket',
				'type' => 'select',
				'options' => array(
					array('name' => 'Stainless Steel', 'value' => 'stainless-steel'),
					array('name' => 'E-Coat (Black)', 'value' => 'e-coat-black'),
					array('name' => 'Option Three', 'value' => 'none')
				)
			),
			array(
				'name' => 'Weight per Axle Set',
				'desc' => 'e.g., 45 LBS.',
				'id' => $prefix . 'weight_per_axle_set',
				'type' => 'text'
			),
		),
	);

	$meta_boxes
}

// Initialize the metabox class
add_action( 'init', 'wps_initialize_cmb_meta_boxes', 9999 );
function wps_initialize_cmb_meta_boxes() {
	if ( !class_exists( 'cmb_Meta_Box' ) ) {
		require_once(CHILD_DIR . '/lib/metabox/init.php');
	}
}