If you’re building websites for small businesses, who are working under a tight budget but would like content management, you can’t do much better than the open source system WordPress. The PHP-based system became incredibly popular when blogs were making a big impact on the Web and in recent years there have been many improvements which have made the system a big competitor as a fully fledged content management.

WordPress as a content management system is far from perfect. There are plenty of reasons why it isn’t the best choice in a lot of situations and there are a lot of annoying idiosyncrasies. However, for small websites which require content management, the easy to use administration area in which clients can feel confident with and the recent improvements mean that it is a system you should persevere to learn and master.

The have been many recent improvements, but it is the addition of custom post types, which mean you can define your own content areas instead of being limited to the default posts and pages without having to rely on hacks and plugins, that has turned WordPress from a blog service in to a fully fledged content management system.

Custom post types have been available in WordPress since version 2.9, however it was version 3 and the most recent 3.1 release which matured them and made them more usable. These new post types mean you can easily add consistent sets of data, for example a list of employees at a company, including permalink pages, featured image, snippets and even tags.

’.RunSpanGamut(UnslashQuotes(‘Custom Post Type Example’)).’

In a recent project I needed information about team members, including an archive page which listed them. I first added a page called “Team” and created a template file page-team.php. I also registered the custom post type in the functions.php. Below is the custom post type snippet;

function post_type_init() {
    register_post_type('team', array(
    'labels' => array(
            'name'                  => __( 'Team' ),
            'singular_name'         => __( 'Team' ),
            'add_new'               => __( 'Add New Member' ),
            'add_new_item'          => __( 'Add New Member' ),
            'edit'                  => __( 'Edit Member' ),
            'edit_item'             => __( 'Edit Member' ),
            'new_item'              => __( 'New Member' ),
            'view'                  => __( 'View Member' ),
            'view_item'             => __( 'View Member' ),
            'search_items'          => __( 'Search Team' ),
            'not_found'             => __( 'No Members found' ),
            'not_found_in_trash'    => __( 'No Members found in Trash' ),
            'parent'                => __( 'Parent Member' ),
    'description'           => 'A team member',
    'capability_type'       => 'post',
    'public'                => true,
    'exclude_from_search'   => false,
    'show_ui'               => true,
    'hierarchical'          => false,
    'has_archive'           => false,
    'rewrite' => array(
        'slug'          => 'team',
        'with_front'    => false,
    'supports' => array(
        'title', 'editor', 'excerpt', 'thumbnail', 'page-attributes'

For more indepth documentation about custom post types including what each value means, their options and more arguments, check out the register_post_type Codex page.

’.RunSpanGamut(UnslashQuotes(‘Custom Fields’)).’

The system also makes it easy for you to expand custom post types with custom fields, giving even more flexibility to the content. For each team member I needed to add more specific content, such as their role and age.

I get all the current custom data for this entry, checking that the data exists and assigning it to a variable to use. Then it is just a case of building the HTML you want to display, these can be any form element you want (input, textarea, checkbox etc).

function post_type_custom_fields_team() {
    global $post;

    $custom = get_post_custom($post->ID);
    $role   = !empty($custom['role'][0]) ? $custom['role'][0] : '';
    $age    = !empty($custom['age'][0]) ? $custom['age'][0] : '';

    echo '<p><label for="role">Role:</label><br />';
    echo '<input type="text" style="width:90%;" id="role" name="role"
            value="' . attribute_escape($role) . '" /></p>';

    echo '<p><label for="age">Age:</label><br />';
    echo '<input type="text" style="width:50px;" id="age" name="age"
            value="' . attribute_escape($age) . '" /></p>';

I use a single function to wrap all the custom field add_meta_box functions and a single function to save all the data, as it makes registering them with WordPress easier. The first five arguments are the ones you will probably change the most. As defined by the add_meta_box Codex page, these are; the meta box identifier (must be unique per post type), title of the meta box, the callback function which adds the extra fields, the type of write screen or custom post type and context which is where the box is added (‘normal’, ‘advanced’, or ‘side’).

The add_meta_box function is as follows;

function post_type_custom_fields() {
    add_meta_box('extra', 'Details', 'custom_fields_team', 'team', 'normal');

Finally, you need to tell WordPress about these functions, which is done by adding “actions”. For the custom post type you need one action, and custom fields you need two. For the code above, the following code was used;

add_action('init',       'post_type_init');
add_action('admin_init', 'post_type_custom_fields');
add_action('save_post',  'post_type_custom_fields_update');

Note, when registering a custom post type you can use the register_meta_box_cb argument, which points to a function where you setup meta boxes. This may be a preferable solution to that documented above, but I didn’t know about it before!


Although a great addition to WordPress, there are plenty of annoyances which plague the custom post type system. In version 3, the system lacked a built in archive system, however this was implemented in version 3.1. This quick turn around is noticeable and in many situations it’s current form is more hassle than it’s worth.

The lack of custom post type archive in version 3 lead to solutions using the built in pages and templates functionality. This is a system I still use even though archives are available in version 3.1. I found the built in archive solution for custom post types to be very buggy and lacked functionality I required.

Firstly, there is currently no integration with the (relatively recent) Menu / Navigation system, meaning you had no way to adding these archive pages to the template dynamically (sure, there might be hacks, but no nice way). The only viable method was adding a custom page, but then this lost any relationship to the active page/archive, making coding active states reliant on menu ID class names, which can lead to difficult to maintainable code.

Using a page with a custom page template means you can also easily add in other plugins, such as All-in-One SEO, allowing for control over the title and meta data for the page. It also means that you can use the managed content area as you would on a normal page.

Combining the template page with the rewrite functionality of the custom post type gives you the most control over your new content. You can even change the permalink template by using the single-{post_type}.php template hierarchy.

Although using two sections in the CMS to manage a single entity is far from ideal, it gives the flexibility and functionality the current implementation of custom post types is missing. I have no doubt in future releases these issues with be rectified, it’s just a case of waiting and using other solutions in the mean time.