cc licensed flickr photo shared by TakenByTina

My previous post just outlined the kinds of things I put into a new site created with a beta version of WordPress 3 (I started with the first beta and honestly, it had more polish than most finished products) – I actually did not tell you much. Now it’s time to get out and start hacking.

In this post, I’ll detail what I did to create three content types on the MIDEA site. You will see code, raw PHP out in the open.

While there is an excellent plugin for creating custom content types (I did try it out and also parsed through the code to see what it did), it only did about 15% of what I wanted. Creating the content types are easy. But the plugin does nothing to help you add the form elements to create, edit the extra meta data I needed to add to the content.

Please put your safety goggles on now.

It’s All About the functions.php, Baby

This was a huge, and seemingly “doh”-like realization of the power of a single file inside most “beyond the basic” WordPress templates. The purpose of functions,php is to have a place to put functions you can call from anywhere in your templates. I’d seen it in there and noted that it typically did a bunch of formatting tasks for a template, or some custom things to handle the way a template needed to fish through Pages.

But I’d done a of of template hacking and left a lot of code in the templates that would have been cleaner and more re-useable in this file. But we can do it for much more, and this is where we will put the code to create my three content types.

I will admit that what I am spelling out here is a mashup of some code I found elsewhere and mostly what I kept banging on til it works. I know nothing about the WordPress process flow, and something I did may not be fullykosher or optimal.

Thats my disclaimer.

We start simple- to create our content types we need to add a few “hooks” or places in the WordPress normal flow opf whatever it does, that it does a few extra things for us.

< ?php
// create the midea content types
add_action('init', 'midea_custom_init');
?>

All we are saying is that when WordPress gets going (init) we want to add a “hook” to run the code we will out in a function named midea_custom_init. Its good practice to prefix your functions in this file with something unique that may collide with other WordPress code. I’ve gone the route of naming all my code functiions (well, most of them) with a “midea” prefix.

It’s in this new function that we launch our new content types. We need to do a few things for each custom content type:

  • register it with wordpress (turn it on)
  • set up any taxonomy (tagging capability) we may want. I set this up, but have yet to implement it.
  • activate rewrite rules so WordPress hands off URLs correctly for the new content type.
  • set up the template redirects so our content types have their own templates.

To our code above, we will add our midea_custom_init function, first to set up the content type for organizations

< ?php
// create the midea content types
add_action('init', 'midea_custom_init');

function midea_custom_init() {
// create the custom types for MIDEA 

// create ORGANIZATION
    $args = array(
		'label' => __('Organizations'),
		'singular_label' => __('Organization'),
		'public' => true,
		'show_ui' => true, // UI in admin panel
		'_builtin' => false, // It's a custom post type, not built in!
		'_edit_link' => 'post.php?post=%d',
		'capability_type' => 'post',
		'hierarchical' => false,
		'rewrite' => array("slug" => "org"), // Permalinks format
		'supports' => array('title', 'editor', 'thumbnail')
    );

    register_post_type( 'org' , $args );

    register_taxonomy( 
		'mtype', 
		'org', 
		array ('hierarchical' => false, 'label' => __('Museum Types'), 
		'singular_label' => __('Museum Type'),
'query_var' => 'mtype')
	);
	add_new_rules('org');
}
?>

The steps are to set up $args as a holder for the arguments we pass next to register_post_type that tells WordPress, “hey, set this up.” In args, they key things to change for your own are the labels, define a “slug” or the part of the url that will preface whatever content is created (e.g. for MIDEA, all organizations will have URLs like http://midea.nmc.org/org/xxxxxxxx). The list of things for supports are the bits of a basic blog post that we will use, in this case we are using the title, editor (to create the body text), and thumbnail (icon to represent the content). See the documentation to see the other things you can use.

When I call register post type, the first parameter (‘org’) is how the content will be marked in the database (to differentiate from blog posts as type “post’ and Pages as ‘page’). I included the code to set up a taxonomy for my content, tags that could be applied in the editor (I have yet to use this, but added it just as wel. I then have a call to a new function we ‘ve not listed yet, add_new_rules which nudges wordpress to resolve URLs correctly.

That;s one content type, I add in similar code to fill out the function for my other two content types.

< ?php


// create the midea content types
add_action('init', 'midea_custom_init');


function midea_custom_init() {
// create the custom types for MIDEA 

	// create ORGANIZATION
    $args = array(
		'label' => __('Organizations'),
		'singular_label' => __('Organization'),
		'public' => true,
		'show_ui' => true, // UI in admin panel
		'_builtin' => false, // It's a custom post type, not built in!
		'_edit_link' => 'post.php?post=%d',
		'capability_type' => 'post',
		'hierarchical' => false,
		'rewrite' => array("slug" => "org"), // Permalinks format
		'supports' => array('title', 'editor', 'thumbnail')

    );

    register_post_type( 'org' , $args );
    
	register_taxonomy( 
		'mtype', 
		'org', 
		array ('hierarchical' => false, 'label' => __('Museum Types'), 
		'singular_label' => __('Museum Type'),
'query_var' => 'mtype')
	);
	
	add_new_rules('org');

	// create PROJECTS
    $args = array(
		'label' => __('Projects'),
		'singular_label' => __('Project'),
		'public' => true,
		'show_ui' => true, // UI in admin panel
		'_builtin' => false, // It's a custom post type, not built in!
		'_edit_link' => 'post.php?post=%d',
		'capability_type' => 'post',
		'hierarchical' => false,
		'rewrite' => array("slug" => "proj"), // Permalinks format
		'supports' => array('title', 'editor', 'thumbnail')
    );

    register_post_type( 'proj' , $args );
    
	register_taxonomy( 
		'ptype', 
		'proj', 
		array ('hierarchical' => false, 'label' => __('Project Types'), 
		'singular_label' => __('Project Type'),
'query_var' => 'ptype')
	);
	
	add_new_rules('proj');

	// create EVENTS
    $args = array(
		'label' => __('Events'),
		'singular_label' => __('Event'),
		'public' => true,
		'show_ui' => true, // UI in admin panel
		'_builtin' => false, // It's a custom post type, not built in!
		'_edit_link' => 'post.php?post=%d',
		'capability_type' => 'post',
		'hierarchical' => false,
		'rewrite' => array("slug" => "event"), // Permalinks format
		'supports' => array('title', 'editor', 'thumbnail')
    );

    register_post_type( 'event' , $args );
	
	// set up URL redirects
	add_new_rules('event');

        // set up redirects
	add_action("template_redirect", 'midea_template_redirect');
}

(note I did not set up taxonomies for events, no need seen). At the very end is one more step, another WordPress hook to tell WordPress how to handle templates for my content types.

What we have to add next are some of the functions used in the above code.

First, we must have WordPress track the URLs it sees coming in by adding “redirects”, things that take a public URL like http://midea.nmc.org/proj/some-groovy-proj and turn it into something WordPress knows how to handle. We’ve called it once for each content type, passing the content type as a parameter.

I honestly cannot explain fully what this does, I adapted it from one of the articles I read before starting out.

function add_new_rules($ptype){

	global $wp_rewrite;

	$rewrite_rules = $wp_rewrite->generate_rewrite_rules("$ptype/");
	$rewrite_rules["$ptype/?$"] = 'index.php?paged=1';

	foreach($rewrite_rules as $regex => $redirect)
	{
		if(strpos($redirect, 'attachment=') === false)
			{
				$redirect .= "&amp;amp;amp;amp;post_type=$ptype";
			}
		if(0 < preg_match_all('@\$([0-9])@', $redirect, $matches))
			{
				for($i = 0; $i < count($matches[0]); $i++)
				{
					$redirect = str_replace($matches[0][$i], '$matches['.$matches[1][$i].']', $redirect);
				}
			}
		$wp_rewrite->add_rule($regex, $redirect, 'top');
	}
}

And the last function to add sets up the templates for our content types. What this will do is for my content type named proj it creates an expectation that there will be a template to display all the projects named proj.php and another template to display a single project called single-proj.php

function midea_template_redirect()
{
	global $wp;
	
	$midea_custom_types = array('proj', 'org', 'event');
	
	if (in_array($wp->query_vars["post_type"], $midea_custom_types)) {
	
		if ( is_robots() ) :
			do_action('do_robots');
			return;
		elseif ( is_feed() ) :
			do_feed();
			return;
		elseif ( is_trackback() ) :
			include( ABSPATH . 'wp-trackback.php' );
			return;
		elseif($wp->query_vars["name"]):
			include(TEMPLATEPATH . "/../midea/single-".$wp->query_vars["post_type"].".php");
			die();
		else:
			include(TEMPLATEPATH . "/../midea/".$wp->query_vars["post_type"].".php");
			die();
		endif;
	}
}

Notes- I lifted this from another site, but found I needed to add a global $wp; statement to get it to work. Also you may note the curious paths in the latter two statements; this is because (to be written up in a later post) I have made a midea theme that is a child of the primary theme I am using. TEMPLATEPATH points to the location of my Modularity template, so the “/../midea” goes up a directory and back down.

And I can spot some sloppiness in that last function- I have the three content types hard coded in as an array; I should have made midea_template_redirect() a function I passed an argument to like I did for add_new_rules($arg). It’s always something.

If all goes well and I;ve not left off a critical piece, we should be able to see our new content types appear in your WordPress dashboard with the same edit/create buttons as we have for blog posts and pages, plus taxonomy tabs for the 2 content types we enabled. We could even create content, but at this point, they have nothing different from posts/pages.

The next installment will go even deeper and get us to the point of having snazzy menus, fields, etc attached to our custom content types for entering/editing the custom meta data. I’ve attached a copy of the functions.php you would have done at this point.

So who’s with me up to here?

It gets more fun as we go deeper, but I’ll be taking a break next few days, and will return with a freshly oiled chainsaw.

You can find all the posts in this series at http://cogdogblog.com/2010/07/23/roundup-wp3/

The post "Setting up Custom Content Types in WordPress 3.0" was originally squeezed out of the bottom of an old rusted tube of toothpaste at CogDogBlog (http://cogdogblog.com/2010/05/setting-up-custom-contenttypes/) on May 28, 2010.

20 Comments

  • Building a Site with New WordPress 3.0 Content Types: Part 1 of Several - CogDogBlog cogdogblog.com/2010/05/28/wordpress-3-pt1

    […] Creating the content types. The easiest part […]

    • Cathy Finn-Derecki cderecki.umwblogs.org

      This has been wonderful, and I have to thank you (and Jim Groom, and Martha Burtis). I have one question: I’d like to customize the nature of the comments for this custom post type. We’ve developed a really simply Classfied ad custom post type. I’d like to allow comments (in this case, inquiries about the product), but have the option to have them sent privately to the author, not displayed on the Web site. I’m thinking that I can just suppress the display with css, but that seems clunky. Any ideas?

      • Alan Levine aka CogDog cogdogblog.com

        It’s easier than that– just modify the template that displays the post, usually single.php, and remove the parts the display the comments. This way comments come in, but are never displayed.

  • Jim Groom bavatuesdays.com

    I’m up with you, and I am loving it. This is a tour de force series, that may very change the way we use WOrdPress on UMW Blogs in the coming year. Working with faculty on sites where we can customize content to this degree, and truly frame the publishing space for their ideas and projects could very well make this platform even more popular than it already is—which is crazy to think about.

    What’s more, your play-by-play is crystal clear, and I am hacking my own site/template along side this series so that I have a sense of the code. I also like that I am finally being forced to figure out what child themes are, saw that in the /../../midea code, and glad you are giving me a crash course in WP 3.0—dog, you rule!

  • Stephen Downes downes.ca

    This is great. This is what I have done with my own website, and a functionality WordPress (and pretty much every CMS) has been missing for years.

    Your next challenge- create and consume custom RSS formats. Like this: http://www.downes.ca/schema/event/

    • Alan Levine aka CogDog cogdogblog.com

      Actually drupal has had this capability for a while. But my thoughts on drupal will come at the end of the series. As Boris Mann kept saying at Northern Voice, “It’s fire!”

      Doing a custom feed format is not a big deal; just another template

  • Stephen Downes downes.ca

    Drupal only sort of has the capability, with CCK. But the implementation is very awkward, and when I tried it a while back it couldn’t support very much traffic. That may be an issue with the WordPress functionality as well, I don’t know.

    Producing a custom RSS feed is easy; as you say, just another template. Consuming it and storing the data properly is a different matter. Though if that too is easy with WordPress I would be very happy.

  • Jon Kirkman surroundview.net

    Thanks for the write-up so far!
    I’m looking forward to the rest of the series.
    I can’t think of a site we’ve built that this wouldn’t have saved us hours of headache.

    I find it curious that there are already several plugins for WP-3 that add a UI for this step but don’t really address creating proper custom fields to accompany the new custom post types.

    Thanks again.

  • Those Darn Kids: WordPress Child Themes - CogDogBlog cogdogblog.com/2010/05/29/those-darn-kids

    […] Creating the content types. The easiest part […]

  • Glen

    I’m very new to wordpress, and I’m a little stuck following the instructions. I’m trying to implement the above for some custom content types of my own. when I update functions.php I get an error:

    Call to undefined function add_action()

    The error is referring to this line in my code:

    add_action(‘init’, ‘cdem_custom_init’);

    Am I doing something wrong?

    Cheers

  • Put on Your HazMat Suits- Setting Up Metadata For WordPress 3 Custom Content Types - CogDogBlog cogdogblog.com/2010/06/06/put-on-your-hazmat-suits

    […] custom content types, same thing), I overviewed the plans for the MIDEA web site, we set up the places to create the new content types, and diverged into some set up magic using child […]

  • Dressing up and Displaying WordPress 3 Custom Post Content (a) - CogDogBlog cogdogblog.com/2010/07/21/dressing-up-wp3-content-a

    […] this series, I started with an overview of the plans for the MIDEA project, we set up the places to create the new content types, and introduced the benefits using child themes. Then we took it up a notch to dive into the code […]

  • Alan,

    Excellent stuff here. I think you left out two functions from the functions.php.

    function midea_proj_options() {
    // add meta data fields for a MIDEA organization content type
    global $post;

    // Use nonce for verification
    echo '';

    $my_fields = midea_proj_fields(); // fields for this content type

    foreach ($my_fields as $key => $value) {
    // load values into our array
    $my_fields[$key] = get_post_meta($post->ID, 'midea-proj-' . $key, true);
    }

    echo '

    Organization


    ' . "\n";
    echo '

    :

    ' . "\n";
    echo '

    :

    ' . "\n";
    }

    function midea_event_options() {
    // add meta data fields for a MIDEA organization content type
    global $post;

    // Use nonce for verification
    echo '';

    $my_fields = midea_event_fields(); // fields for this content type

    foreach ($my_fields as $key => $value) {
    // load values into our array
    $my_fields[$key] = get_post_meta($post->ID, 'midea-event-' . $key, true);
    }

    echo '


    ' . "\n";
    echo '


    ' . "\n";
    echo '

    :

    ' . "\n";
    echo '

    Location

    :

    ' . "\n";

    I was getting an error about expecting a valid callback function or something until I added these to the functions.php file. Now to figure out how to allow for another rich text editing box.

  • Roundup WordPress 3.0 Custom Content Type - CogDogBlog cogdogblog.com/2010/07/23/roundup-wp3

    […] Creating the content types. The easiest part, and the beauty of functions.php […]

  • Wordpress 3.0 Features for Designers | StylizedWeb.com stylizedweb.com/2010/10/26/wordpress-3-0-features-for-designers

    […] If you are the type that likes to get your hands dirty I recommend you read this great article about setting up custom content types and fields. […]

  • Pinning WordPress 3 Custom Content to the Map (b) - CogDogBlog cogdogblog.com/2010/07/22/wp3-custom-content-map

    […] up to this, I began by overviewing the plans for a new web project, we set up the places to create the new content types, and introduced the benefits using child themes. Then we dove headfirst into the code to create the […]

  • Thanks, this is good stuff to get my head into! This really makes WordPress more of a CMS which is great.

  • Custom Content Types y Custom Taxonomies en Wordpress | Max Villegas maxvillegas.com/recursos-y-tutoriales/custom-content-types-y-custom-taxonomies-en-wordpress

    […] Setting up custom contenttypes […]

Leave a Comment

All fields are required. Your email address will not be published.