"
Quite possibly the best addition to WordPress 3.0 was that of Custom Post Types. This took WordPress from being a CMS that can manage posts and pages to being able to manage anything the user can think of rather easily. You no longer have to add custom fields to posts- you can add high level support to your own types, creating their own theme page files and admin areas. One of the first things I did using custom post types was revamp my portfolio and today I’m going to show you how I did it!
We recently showed you a few neat tools for creating “Instant” Custom Post Types. Today is all about actually building a project using CPT’s from scratch… We’ll be doing everything from creating the custom type – to styling it for use in the theme. The CSS (and CSS3!) I use should be generic enough for most themes, but in case it isn’t, feel free to change it! This is intended to be used as a launchpoint for everyone, so feel free to get creative!
Getting Started A Few Thoughts
The first thing to note is that when creating a Custom Post Type, we have a couple of options as to how we want to approach them; The two main implementations you might consider are:- As part of the current theme (usually through the functions.php file)
- As it’s own stand-along plugin.
Developer’s Note: Because we’ll be building this directly into our theme, that doesn’t mean that this is the right way for you though… consider the end-use of your theme. If there’s a good chance that your users are going to be switching themes in the future, or you’re releasing your theme as a public or premium product for lots of people to use, you’ll probably want to build in your custom post type as a standalone file that people can take with them without too much digging through your code.
Just think of it this way, if your theme is the only thing that’s loading your Custom Post Types and you change themes, the Custom Post Type data won’t be usable; It’ll still exist on the database, but it won’t show up in new themes in any meaningful way. Sometimes this isn’t possible if you’re really overhauling a theme to use a CPT by including lots of customizations and templates, but at least consider what users might need to do to preserve the use of their data in the long run.
Let’s get started then!
Step 1 Set-Up
As I stated above, we’ll be adding this to our current theme (I’m using my own custom theme), so the first thing we need to do is go to our theme and open up the functions.php file. We’re going to put our custom post type code in a different file (just so it’s easier to read/manage), so we’ll call that file at the top of our functions file:require_once('portfolio-type.php');Now we’re going to add two files to our theme: portfolio-type.php and portfolio.css. As you can probably guess, all of our CSS for the new type will go in the latter file.
Step 2 Registering the New Type
Thumbnail & Featured Image Support
Before we register the new type, we need to add support for an integral part of the portfolio presentation- featured images. After adding the opening and closing php tags to portfolio-type.php, add the following code:if ( function_exists( 'add_theme_support' ) ) { add_theme_support( 'post-thumbnails' ); set_post_thumbnail_size( 280, 210, true ); // Normal post thumbnails add_image_size( 'screen-shot', 720, 540 ); // Full size screen }These lines, after checking to make sure your particular install supports post thumbnails, will add them to your current theme, then set a couple of default sizes.
set_post_thumbnail_size()
will, as the name suggests, set the default size for the thumbnail. The next line (add_image_size()
will create another image that we can call named ‘screen-shot’, which will be 720×540. We will be using these images when displaying our portfolio.Creating a New Post Type
Right here is where the magic happens- we will now tell WordPress about our new post type. Add the following code to portfolio-type.php:add_action('init', 'portfolio_register'); function portfolio_register() { $args = array( 'label' => __('Portfolio'), 'singular_label' => __('Project'), 'public' => true, 'show_ui' => true, 'capability_type' => 'post', 'hierarchical' => false, 'rewrite' => true, 'supports' => array('title', 'editor', 'thumbnail') ); register_post_type( 'portfolio' , $args ); }The first line here is a hook in WordPress that will call our function,
portfolio_register()
on initialization. The function itself sets up an array of arguments to send with our custom post type. Most notably, we’re setting our admin labels, giving this type all of the capabilities of a standard WordPress post, allowing URL rewrites (for pretty permalinks), and adding support for the title, editor, and featured image fields. You can read more about all of the arguments for register_post_type()
here.After setting up the arguments array ($args), we pass that along with the type name to the function
register_post_type()
.Adding a Custom Taxonomy
The last thing we’ll do in this section is create a custom taxonomy for our new type. Add the following line of code to your portfolio-type.php file:register_taxonomy("project-type", array("portfolio"), array("hierarchical" => true, "label" => "Project Types", "singular_label" => "Project Type", "rewrite" => true));This will create the new taxonomy ‘project-type’ and apply it to the post type ‘portfolio’. You can read more about
register_taxonomy()
here.Step 3 Adding Custom Fields
Creating the Custom Field Box
We wouldn’t have much of a custom type without having some extra info to add to the post. We’ll add that info in the form of custom fields. Specifically, we’ll be adding one extra field for a link to more info about the project, or to the project itself. Add the following code to your portfolio-type.php file:add_action("admin_init", "portfolio_meta_box"); function portfolio_meta_box(){ add_meta_box("projInfo-meta", "Project Options", "portfolio_meta_options", "portfolio", "side", "low"); } function portfolio_meta_options(){ global $post; if ( defined('DOING_AUTOSAVE') && DOING_AUTOSAVE ) return $post_id; $custom = get_post_custom($post->ID); $link = $custom["projLink"][0]; ?> <label>Link:</label><input name="projLink" value="<?php echo $link; ?>" /> <?php }
admin_init
to call our function portfolio_meta_box()
when the WordPress admin is created. Our function will add another box to our portfolio type, which can be populated with anything. What the box is populated with is covered by the 3rd argument, which is a callback function. In this case, our function is named portfolio_meta_options()
.In
portfolio_meta_options()
we’ll create a form field that will be used to capture the project’s link. The first thing we do is grab the global $post
array so we can get the custom fields for whatever post we’re editing. In our next line, we’re checking to make sure WordPress isn’t currently saving the post or custom fields; if it is, we may see inaccurate results when we do grab the custom data.If WordPress is not doing a save, we grab the custom fields for the current post and create a form field using that info.
$custom
(what’s returned from get_post_custom()
is a 2D array where the key is whatever we name the form field for our custom in. Take a look at our text box for the link. You’ll notice that the name matches the index we call in our $custom
array. You’ll also notice that we don’t have a separate form or submit button. This field is added to the form used to edit the entire post.While we only create one here, you can obviously create as many as you’d like.
Saving the Custom Data
Now that we’ve created our custom meta box, it’s time to create a function that will save the information. Add the following lines of code to your portfolio-type.php file:add_action('save_post', 'save_project_link'); function save_project_link(){ global $post; if ( defined('DOING_AUTOSAVE') && DOING_AUTOSAVE ){ return $post_id; }else{ update_post_meta($post->ID, "projLink", $_POST["projLink"]); } }First, as usual, we have our hook- this time to call our function when the post is saved. In the function itself, we again grab the
$post
array so we can get the post ID and check to see if the post is autosaving. If we don’t include this line, we will lose our data, so it’s important we keep that in.If the post is not updating, we save our custom fields using
update_post_meta()
, sending the post id, the name of the custom field, and the new value.Step 4 Customizing Admin Columns
Here we’re going to customize the list displaying all of our projects. Add the following code to your portfolio-type.php file:add_filter("manage_edit-portfolio_columns", "project_edit_columns"); function project_edit_columns($columns){ $columns = array( "cb" => "<input type=\"checkbox\" />", "title" => "Project", "description" => "Description", "link" => "Link", "type" => "Type of Project", ); return $columns; } add_action("manage_posts_custom_column", "project_custom_columns"); function project_custom_columns($column){ global $post; switch ($column) { case "description": the_excerpt(); break; case "link": $custom = get_post_custom(); echo $custom["projLink"][0]; break; case "type": echo get_the_term_list($post->ID, 'project-type', '', ', ',''); break; } }
project_edit_columns()
), we do something pretty interesting in terms of editing the columns. WordPress places the columns for a post type’s display list into an array with a key and a value. We create those key-value pairs in this function. In the next function (project_custom_columns()
), we use a switch statement to grab the key and then display the information we want based on it. As you can see, just like in the previous section, we get the global $post
array so we can get the post’s ID to properly display any custom information, like our link and taxonomy.You’ve probably noticed that the number of cases does not match the number of columns in our
$columns
array. That’s because for certain keys, like cb and title, WordPress has default values that we don’t want to overwrite.Step 5 Adding Some Display Functions
Before we get to creating a template page, I want to give you some functions I included to get displaying the projects just right. Open up your functions.php file and add the following three components:add_filter('excerpt_length', 'my_excerpt_length'); function my_excerpt_length($length) { return 25; } add_filter('excerpt_more', 'new_excerpt_more'); function new_excerpt_more($text){ return ' '; } function portfolio_thumbnail_url($pid){ $image_id = get_post_thumbnail_id($pid); $image_url = wp_get_attachment_image_src($image_id,'screen-shot'); return $image_url[0]; }The first two hook/function pairs are common to WordPress- they simply change the length of the excerpt and the “more text” indicator, which I’ve replaced with just a space. Please note that this will happen to all posts, not just the portfolio post, and that if you’re using a child theme, this may not work.
The custom function,
portfolio_thumbnail_url()
takes in a post’s ID as the argument and grabs the screen-shot version of the image we uploaded. We’ll be calling it in our template page, so keep an eye out for it!Step 6 Create a Template Page
Now that we have our custom post type all set up in the admin, it’s time to create a theme page so we can display them. To accomplish this, we’re going to create a template with the name, “Portfolio” and then assign the template to a page in WordPress. While we can also create archives-portfolio.php to accomplish the same thing, we won’t be able to add that page to a menu from the WordPress admin, so this route is best. Keeping with WordPress naming conventions, create a new file called page-portfolio.php and add the following code:<?php /* Template Name: Portfolio */ get_header(); query_posts('post_type=portfolio&posts_per_page=9'); ?>
<div id="portfolio" class="group"> <h2>Portfolio of Work</h2> <div class="group"> <?php if (have_posts()) : while (have_posts()) : the_post(); ?> <?php $title= str_ireplace('"', '', trim(get_the_title())); $desc= str_ireplace('"', '', trim(get_the_content())); ?> <div class="item"> <div class="img"><a title="<?=$title?>: <?=$desc?>" rel="lightbox[work]" href="<?php print portfolio_thumbnail_url($post->ID) ?>"><?php the_post_thumbnail(); ?></a></div> <p><strong><?=$title?>:</strong> <?php print get_the_excerpt(); ?> <a title="<?=$title?>: <?=$desc?>" rel="lightbox[work]" href="<?php print portfolio_thumbnail_url($post->ID) ?>">(more)</a></p> <?php $site= get_post_custom_values('projLink'); if($site[0] != ""){ ?> <p><a href="<?=$site[0]?>">Visit the Site</a></p> <?php }else{ ?> <p><em>Live Link Unavailable</em></p> <?php } ?> </div> <?php endwhile; endif; ?> </div> </div> <?php get_footer(); ?>You’ll notice a few things here: first, after we’re in the loop, we need to trim extraneous ‘”‘ characters from our title and content using
str_ireplace()
. I’m not exactly sure why they appear here, but from what I can tell this is a necessary step. You should also notice that we’re using lightbox. While we could add it ourselves (which I’d probably recommend if this were a plugin), since we are modifying our own theme, we could download one of the many lightbox plugins available in the WordPress repository. Just pick your favorite!Aside from that, this should look familiar to anyway who’s worked with the WordPress loop. What we’re doing here is creating blocks with our thumbnails and descriptions, which link to (using lightbox) the screen shot for each of the 9 projects. I didn’t include links to other pages (in case you have more than 9 projects) because I only want my users to see latest 9 projects. You can allow users to get to other posts using
posts_nav_link()
, if you so choose.I should also note that I removed WordPress’s default more link because it linked to a single post page, and I wanted to use lightbox, so I constructed my own.
Step 7 Styling our Portfolio Type
Here’s the fun part: making our portfolio type look pretty. Here’s some CSS that I’ve included- you can add it to the bottom of your style.css sheet, or into our newly created portfolio.css file. Just be sure to use@import
to call portfolio.css at the top your theme’s style.css (it will not work anywhere else):.item{ float: left; margin: 5px; width: 310px; background: #EFEFEF; -moz-border-radius: 7px; -webkit-border-radius: 7px; border-radius: 7px; text-align: center; -moz-box-shadow: 0px 0px 6px #999; -webkit-box-shadow: 0px 0px 6px #999; box-shadow: 0px 0px 6px #999; } .item p{ text-align: left; } .item p a{ text-align: left; font-weight: bold; } .item img{ margin-top: 5px; text-align: center; border: 1px solid #000000; max-width: 280px;} /* self-clear floats */ .group:after { content: '.'; display: block; height: 0; clear: both; visibility: hidden; }If you’ll reference our template page, you’ll see that each project is wrapped in a div called “item,” which we now apply CSS to. Since no two themes are the same, you may have to tweak your own CSS a bit, but here is a nice starting point for you. I’ve also included a class for self-clearing floats. This is a technique I got from Dan Cederholm, and I think it’s a bit more elegant that the standard “clearfix” method of clearing content after floating divs.
Conclusion
That’s it! You now have a simple portfolio using custom post types. While this was built to serve my needs, and I wanted to keep things simple, the possibilities for this are endless as you can tailor it to fit whatever kind of portfolio you have (writing/articles, photography, graphic design, etc.). If you don’t want to use lightbox to display the single project, you can also create a theme page with the name single-portfolio.php, which will then apply the code within to each individual project, much like single.php does for blog posts."