Create A Product Listing With Infinite Categories Using Custom Post Types

Create A Product Listing With Infinite Categories Using Custom Post Types

Tutorial Details
  • Program: WordPress
  • Version: 3.3 (always update)
  • Difficulty: Intermediate
  • Estimated Completion Time: 1-2 hours

In today’s tutorial we’re going to leverage the power of custom post types, taxonomies, and custom loops to create a dynamic products listing. There’s a lot of cover so let’s get straight to it!


Step 1 Creating a Products Custom Post Type

First, we need to create our products custom post type. Open your functions.php and add the following line to the top of the file after the opening PHP tag:

require_once ('functions/products.php');

If you don’t have one already, create a directory called functions inside your theme folder, and a file called products.php inside that directory. In our products.php, we’ll register our custom post type:

add_action( 'init', 'product_register' );
function product_register() {
  $labels = array(
    'name' => _x('Products', 'post type general name'),
    'singular_name' => _x('Product', 'post type singular name'),
    'add_new' => _x('Add New', 'Product'),
    'add_new_item' => __('Add New Product'),
    'edit_item' => __('Edit Product'),
    'new_item' => __('New Product'),
    'all_items' => __('All Products'),
    'view_item' => __('View Products'),
    'search_items' => __('Search Products'),
    'not_found' =>  __('No products found'),
    'not_found_in_trash' => __('No products found in Trash'), 
    'parent_item_colon' => '',
    'menu_name' => 'Products'

  );
  $args = array(
    'labels' => $labels,
    'public' => true,
    'publicly_queryable' => true,
    'show_ui' => true, 
    'show_in_menu' => true, 
    'query_var' => true,
    'rewrite' => true,
    'capability_type' => 'post',
    'hierarchical' => false,
    'supports' => array( 'title', 'editor', 'thumbnail', 'page-attributes' )
  ); 
  register_post_type('products',$args);
}

The first line is a hook that will call our function ‘products_register’ when WP admin is initialized. Within our function we set up two arrays. The first is the labels that will be used throughout our WordPress dashboard. Within our function we create a list of arguments most of which are self-explanatory. Our last argument ‘supports’ allows use to specify what we want to display on the post’s write/edit page. Note that we have added support for page attributes to specify the order of our products within a category (image of panel). We then pass the function register_post_type our custom post type’s name and the arguments.

Order Panel

Let’s add a custom field to store the price of our products.

add_action("admin_init", "price_meta");
 function price_meta(){
  add_meta_box("price", "Price", "Price", "products", "normal", "low");
}

 function price(){
  global $post;
  $custom = get_post_custom($post->ID);
  $price = $custom["price"][0];
  ?>
  <label>Price:</label>
  <input id="price" name="price" type="text"  value="<?php echo $price; ?>" />
  <?php
}
 
add_action('save_post', 'save_meta');
function save_meta(){
  global $post;
  update_post_meta($post->ID, price, $_POST["price"]);
}
?>

Again when WP admin is initialized, our function price_meta is called that will display our custom field. Next we create a function called price that will create the form field for our custom post type. we’ve also grabbed it’s current value using get_post_custom so we can display it when we’re editing a post if a value has been set. Our last function saves the value assigned to our custom field using update_post_meta(). We pass the function our post’s ID, the name of the field being updated, and the value we’re storing.


Step 2 Creating a Products Category Taxonomy

We want to be able to organize our products into categories so let’s create a custom taxonomy called Product Categories so we can do just that.

$labels = array(
    'name' => _x( 'Product Categories', 'taxonomy general name' ),
    'singular_name' => _x( 'Product Category', 'taxonomy singular name' ),
    'search_items' =>  __( 'Search Product Categories' ),
    'all_items' => __( 'All Product Categories' ),
    'parent_item' => __( 'Parent Category' ),
    'parent_item_colon' => __( 'Parent Category:' ),
    'edit_item' => __( 'Edit Product Category' ), 
    'update_item' => __( 'Update Product Category' ),
    'add_new_item' => __( 'Add Product Category' ),
    'new_item_name' => __( 'New Product Category' ),
    'menu_name' => __( 'Product Categories' )
  ); 	

register_taxonomy('product_categories',array('products'), array(
    'hierarchical' => true,
    'labels' => $labels,
    'query_var' => true,
    'show_ui' => true
 ));

Again we define our labels. Next we call the function register_taxonomy. We tell the function the name of our taxonomy which is product_categories (note the lack of spaces and capital letters), the post type(s) to associate the taxonomy with and pass it a handful of arguments. Importantly, we set hierarchical to true to support nested taxonomies or in our case sub product categories.


Step 3 Dealing With Images

Our products will be making use of featured images so we need to make sure our theme supports it. Check your theme’s functions.php and see if it already supports post thumbnails, if not add this line to the top of the file just under where we required our custom post type.

if ( function_exists( 'add_theme_support' ) ) {  
add_theme_support( 'post-thumbnails' );  
}  

We would like assign each of our product categories an image. For now there is no built-in way to do this so we’ll need to install the fantastic Taxonomy Images plugin by Michael Fields. Once we have the plugin installed and activated we navigate to it’s settings page and tell the plugin to add image support to our Product Categories taxonomy.

Taxonomy Images Plugin Settings Page

Next we’re going to add support for dynamic image resizing for our product category and product images using Timthumb, a PHP image resizing script. Timbhumb is a joy to work with and a breeze to implement in WordPress themes. We’ll get into the meat of using it in our theme files soon but for now we’re just going to get it set up. Let’s download Timthumb and place the script in with our theme files. We’ll also need to create a cache directory in our theme’s directory and give it 777 permissions.


Step 4 Listing our Top Level Product Categories

To list our top level product categories we’re going to create a custom page template and call it Products.

<?php
/**
 Template Name: Products
 */

get_header(); ?>

Next we’re going to create a query for our top level categories.

$args=array(
	'hide_empty'		=> 0,
	'parent'		=> 0,
	'taxonomy'		=> 'product_categories');

Here we’ve set up three arguments to pass to the get_categories function. Each argument is essential to retrieving the top level product categories

  • Hide_empty => 0 Allows us to show categories that don’t contain any posts
  • Parent => 0 By defining a parent we’ve limited the taxonomies returned to only taxonomies that have a parent with an ID of 0. By setting the parent ID to 0 it means we’ll return top level taxonomies since they don’t have a parent
  • Taxonomy => ‘product_categories’ here we’re defining the taxonomy to query, by default this is set to “category” so we need to tell WordPress we want to query our custom taxonomy instead

Next we pass the arguments to get_categories which returns an array of matched taxonomies. We start a foreach loop and set a couple of variables.

The first is the product category’s image that we’ve retrieved using get_option and passing it the name of the option we want to retrieve. In this case it’s the image set by the taxonomy images plugin. The next variable is the URL of the taxonomy’s archive. we’ve used the get_term_link function to achieve this by passing it the taxonomy slug and the taxonomy name. Next we start outputting the matched product categories.

$categories=get_categories($args);
		
foreach($categories as $category) {
	echo '<div class="product-cat">'; 

	$thumb_url = get_option('taxonomy_image_plugin');
	$product_cat_url = get_term_link( $category->slug, 'product_categories' );
				
	echo '<a href="'.$product_cat_url.'"><img src="'.get_template_directory_uri().'/timthumb.php?src='.wp_get_attachment_url( $thumb_url[$category->term_taxonomy_id]).'&h=200&w=200" /></a>';
	echo '<a class="cat-title" href="' . $product_cat_url .'">' . $category->name.'</a>';
	echo '</div> <!--end product cat-->';
} 

Let’s take a look at how the thumbnails are generated using timthumb. We need to set the source to the path of our copy of the script in our theme’s directory. Then we need to give the script the source for the image and set it’s width and height. Simple isn’t it? I’ve also set up an if statement to make sure we have a fallback in place if a category doesn’t have an image associated with it.

Next we should add some basic styling to make things take shape. we’ve just floated the divs and absolutely positioned the titles within the divs.

.product, .product-cat{
	border:10px solid #dfdfdf;
	float:left;
	margin:0 40px 20px 0;
	outline:1px solid #bfbfbf;
	position:relative;
}
	.product img, .product-cat img{
		max-width:100%;
	}
	.product-title, .cat-title{
		background:rgba(0,0,0,.8);
		color:#FFF;
		display:block;
		position:absolute;
        bottom:0;
		left:0;
		padding:10px 0;
		text-align:center;
		width:100%;
	}

You’ll notice we’ve added styles for the products lists as well since we’ll be displaying them in a similar fashion to the product categories lists.


Step 5 Listing Subcategories and Products

Create a file called taxonomy-products_categories.php. If you take a look at the template hierarchy you’ll see that this the template we need to create a taxonomy archive. This allows us to create different archives for any custom taxonomy.

The first thing we’ll be doing in this file is querying our product categories again.This query serves two purposes. We’ll be using it to output the next level of product categories if one exists and testing whether or not we’re on the lowest depth category and if that’s the case we’ll need to output that category’s products. So let’s set that up.

$slug = get_query_var( 'term' );
$term = get_term_by( 'slug', $slug , 'product_categories' );
$term_id = $term->term_id;

$args=array(
		'hide_empty'		=> 0,  
		'parent' 		=> $term_id,
		'taxonomy'		=> 'product_categories');
						
$categories=get_categories($args);

First we’ve grabbed the current taxonomy’s slug using get_query_var(). Next we retrieve all term data with get_term_by() by passing it the field we’re supplying, the value of the field and the taxonomy name. With that we can retrieve the current taxonomy’s ID using get_term_by which we need for our arguments array for the parent parameter. Finally we’ve passed our arguments to the get_categories function and defined a variable for the returned array called categories.

Let’s quickly look at the structure we’ll be following now:


$categories=get_categories($args);
if(!$categories){
	// if there are no subcategories output current product category's products
}
else{
	//if there are subcategories output them instead
}

Now we’re going down the route of if the array is empty. If it is, then we’re on a lowest products category level and should output the current categories products. Let’s set up our arguments first:

if(!$categories){ 
		
	//get the product category name
	echo "<h1 class='entry-title'>".$term->name."</h1>";

	$args = array(
	'posts_per_page' => 2, //remember posts per page should be less or more that what's set in general settings
	'paged' => $paged,
	'meta_key' => 'price',
	'orderby' => 'meta_value_num',
	'order' => 'ASC',
	'tax_query' => array(
				array(
				'taxonomy' => 'product_categories',
				'field' => 'slug',
				'terms' => $slug)
		)
	);

Let’s look at the arguments we’ve set up. The first two arguments enable pagination. The next three arguments handle the order of the products. We’re ordering them by the meta key price and also ordering by their price, lowest to highest. If you want to order the products by setting their order when creating a product use the following argument instead:

'orderby' => 'menu_order'

The final array of arguments defines the taxonomy we’re querying and what specific product category we’re querying. We define the product category by passing ‘term’ the slug of the current product category.

Next we pass these arguments to the WP_query class which will fetch the matched posts. All we need to do then is loop through the returned array of posts and output our products.

<?php
$products_query = new WP_Query($args);
	if (have_posts()) :
	while($products_query->have_posts()) : $products_query->the_post(); 

		$post_thumbnail_id = get_post_thumbnail_id();
		$post_thumbnail_url = wp_get_attachment_url($post_thumbnail_id);
?>
		<div class="product">
			<a class="product-title" href="<?php the_permalink(); ?>"><?php the_title(); ?></a>
			<a href="<?php the_permalink(); ?>">
				<img src="<?php echo get_template_directory_uri(); ?>/timthumb.php?src=<?php if($post_thumbnail_url){ echo $post_thumbnail_url; } else {  echo get_template_directory_uri()."/images/fallback.png"; } ?>&h=200&w=200" alt="<?php the_title(); ?>" />
			</a>
		</div>

We’ve retrieved the post thumbnail URL, to use with timthumb, by retrieving its ID and then using wp_get_attachment_url and passing it the ID.

As a fallback, if a category doesn’t contain any products yet, let’s add the following:

<?php endwhile; else: ?>
	<p>Sorry no products were found.</p>
<?php endif; ?>
<?php wp_reset_query();  ?>

Finally we’ll include our pagination links:

<div class="pagination">
	<?php next_posts_link('« More Products', $products_query->max_num_pages) ?>
	<?php previous_posts_link('Previous Products »') ?>
</div>

Now we’re going to go down the other route, if the category did contain sub categories:

<?php
else{ 
		
	//output current category name
	echo '<h1 class="entry-title">'.$term->name.'</h1>';
	foreach($categories as $category) {
		echo '<div class="product-cat">'; 

		$thumb_url = get_option('taxonomy_image_plugin');
		$thumb_url = wp_get_attachment_url( $thumb_url[$category->term_taxonomy_id]);
		$product_cat_url = get_term_link( $category->slug, 'product_categories' );
?>
		
		<a href="<?php echo $product_cat_url; ?>">
			<img src="<?php echo get_template_directory_uri(); ?>/timthumb.php?src=<?php if($thumb_url){ echo $thumb_url; } else{ echo get_template_directory_uri()."/images/fallback.png"; } ?>&h=200&w=200" alt="<?php the_title(); ?>" />
		</a>
		<a class="cat-title" href="<?php echo $product_cat_url ; ?>"><?php echo $category->name; ?></a>
		</div> <!--end product cat-->
				
<?php } ?>

We’ve already covered what happens in the above code in step four so let’s move on the last part of the tutorial.


Step 6 Displaying an Individual Product

If you’ve worked with custom post types before you know what’s coming next. We’re going to create a single-products.php. Similar to our taxonomy-product_categories.php we used for our product categories taxonomy, we can create a custom single post template unique to our custom post type. In that file we’re going to put:

<?php get_header(); ?>

	<?php if ( have_posts() ) while ( have_posts() ) : the_post(); ?>
		
		<?php 
			//get post thumbnail url
			$post_thumbnail_id = get_post_thumbnail_id();
			$post_thumbnail_url = wp_get_attachment_url($post_thumbnail_id);
		?>
		<img id="product-img" src="<?php echo get_template_directory_uri(); ?>/timthumb.php?src=<?php if(!empty($post_thumbnail_url)){ echo $post_thumbnail_url; } else {  echo get_template_directory_uri()."/images/fallback.png"; } ?>&h=300&w=300" alt="<?php the_title(); ?>" />		
		
		<div id="product-desc">
			<h1 class="entry-title"><?php the_title(); ?></h1>
		<?php the_content(); ?>
			
		<?php if( get_post_meta($post->ID, 'price', true)): ?>
			Price: <?php echo get_post_meta($post->ID, 'price', true); ?>
		<?php endif; ?>
			
		<div> 
		 
	<?php endwhile; ?>

<?php get_footer(); ?>

You should be familiar with most of the template tags used here but let’s take a look at the use of get_post_meta that retrieves our price custom field we set in step one. We’ve set up an if statement to check if a price has been set, if one has, we output the price. We do this by passing the function the post ID, the meta key we want to retrieve, and whether we want a single result. We do, so we set it to true.

Again let’s add some basic styles to layout our products:

#prouct-img{
	float:left;
	width:100%;
}
#product-desc{
	float:right;
	width:50%;
}

Wrapping Up

We’ve covered quite a bit here but hopefully now you should have a learned some new techniques and gained an understanding of the power of custom post types, taxonomies, and custom queries. Any questions or ideas for improvements? Let’s discuss them in the comments.

Note: Want to add some source code? Type <pre><code> before it and </code></pre> after it. Find out more
  • Carlor

    I suggest to purchase easy custom post type plugin on code canyon.

  • http://thejimgaudet.com/ Jim Gaudet

    So I have been dealing with this for a while now and the issue with this is the permalink. If I create a custom post type and taxonomies related to that type as you did here and I want to go to my permalink settings and use something like;

    /%category%/%taxonomy1%/%taxonomy1%/%postname%

    All is fine, however the pages start to return 404′s;

    now, if I decide to put something static in the front like;

    /products/%category%/%taxonomy1%/%taxonomy1%/%postname%

    Then all is fine. I could do this on a site that is one language and have no issue, but if I have a multilingual site it becomes very difficult to translate the static text, which is why I would rather use a category or taxonomy..

    Has anyone here created a product/real estate listing for a multilingual site that would translate down to the category without creating 404′s for pages?

    I’m not sure anyone can help, so I haven’t sent any code. If anyone feels nice enough to offer a little help, I can send the code along.

    Thanks in advance,

  • alfi

    Hi Jim Gaudet, You need to utilize flush rule. Just google it.. It may help you Jim.

  • Spencer

    Thanks Barry Walsh, this is a very good tutorial. I have learned a lot from it.

    But I have a question here:

    When you write the argument for the WP-Query, there is a key value pair reads: ‘paged’ => $paged, I do not know where the variable $paged comes.

    Can you explain that a little bit? Thank you very much.

    • http://barrywalsh-portfolio.com Barry Walsh

      Hi Spencer, thanks for the question. $paged is a globablly defined variable in WordPress, more information can be found at http://ifacethoughts.net/2006/04/01/paged/. The variable returns the current page number. Also here’s a useful list of WP global http://ifacethoughts.net/2006/02/25/wordpress-global-variables/

      Hope that helps!

      • Spencer

        Thanks heaps!

    • Michal

      It’s there to help you with pagination. It’s part of the query arguments.

      You’ll also often see
      $paged = (get_query_var(‘paged’)) ? get_query_var(‘paged’) : 1;

      • Spencer

        Thanks Michal.

  • John

    Hey Love the post….just wanted you you to know that you double typed “bottom” in you css example.

    • http://barrywalsh-portfolio.com Barry Walsh

      Cheers John, I’ll pass on word to the editor and they should clean that up. Just noticed “right” is repeated in the last CSS example too.

  • http://www.wordimpressed.com/ Devin Walker

    Hey Great Article, I would also suggest perhaps for those afraid of code to use a custom post types plugin. It makes setting up and managing your post types a whole lot easier (and faster) that code. Also, I’m a bit apprehensive to use and recommend timthumb anymore. I love it’s functional use, but ever since it’s previous version’s vulnerablities I’ve backed off a bit. It is responsible for countless WP sites getting hacked and still to this day users running older versions are suffering.

  • Pingback: Tweet-Parade (no.51 Dec 2011) | gonzoblog.nl

  • DJ

    ALSO, you’ve completely doubled the whole last several paragraphs at the bottom of step 1.

  • Carlos

    Hello Barry, i was wodnering if you could make an article to teach how to create some custom search/filter in wordpress. For example, if it were a portfolio listing and the user want to look only for PSD’s works during November and that were under $500 (something like that)

    Do you think you could create such a tut? Thank you

    • http://barrywalsh-portfolio.com Barry Walsh

      Hi Carlos, I can that no problem. I’ll have to discuss it with Brandon (the site manager) and get back to you then.

      • Carlos

        WOW!! I hope you can do it Barry!

        I think it is kind of like a taxonomy filter with a dropdown or something like that. It would be great that it only shows the taxonomies that have posts on them. For example:

        There are 4 different taxonomies and therefore 4 dropdowns (A B C D), so first the dropdowns will only have options that are currently available. Then, i select option 2 in the dropdown A, so all the others dropdowns update because of that selection. That way it ensures people dont select a combination that does not exist.

        Do you know what i mean? Is that Possible?

        I really look forward to your reply, will you let me know in here if they allow you to do that?

        Thank you
        -Carlos

        • http://barrywalsh-portfolio.com Barry Walsh

          No problem Carlos, it is possible yeah, if I have it right though. You want a chain linked taxonomy filter that goes down so many levels. Trying to make it as dynamic as possible will be the only issue to solve, I’ve done similar before but there was a set depth for the taxonomies.

          • Carlos

            Hey Barry, happy new year!

            Do you have any answers from Brandon yet?

  • Pingback: Make a product listing in Wordpress | | Primux WordpressPrimux Wordpress

  • Olie

    This is probably the most useful Taxonomy / Custom Post tutorial I have searched for… Thank you!

    • Oblik

      Me too ! Thanks so much ;)

  • http://mypi.nl Pim

    Thanks for the great tutorial! I have one question though: what code would I use if I want to display the subcategory name on the single product page? I can’t get it right….

    • Barry Walsh

      Hey Pim,

      Hope you were able to figure it out, if not hopefully you will check back here!

      This should do it:

      $terms = get_the_terms($post->ID, ‘product_categories’);

      foreach($terms as $term){
      echo $term->name;
      }

      So you just pass get_the_terms your post ID and then the taxonomy name. This will return an array so you can print out that and check the values it returns. You’re after the subcategory name so go for $term->name.

      Hope that helps!

  • Scott

    Hey Barry;

    This is a great tutorial! It has really helped lift the “fog” of what exactly custom posts can do. However, I am still not fully grasping the concept. I believe I am over thinking this.

    I am building an ecommerce store for a client, and I want to organize products into categories and sub-categories. For example, parent categories of Accessories, Pants and Shirts, and then say sub-categories of long-sleeve, short sleeve, sweatshirts, etc. under Shirts. How would I go about setting this up? Would I create one Custom Post Type of “Products”, categories of Accessories, Pants and Shirts, and then sub-categories of long-sleeve, short sleeve, sweatshirts, etc…or would I use taxonomies for that last part? Its the use of the taxonomies that I really am not grasping, for some reason. Again, I definitely must be missing something elementary.

    My end goal is to have a set-up similar to the left “menu” here: http://www.suprafootwear.com/shop

    Any help you could lend is GREATLY appreciated!! I have been unable to find any assistance until reading this. Thank you again for the post. I look forward to hearing from you soon!

    Scott

    • http://barrywalsh-portfolio.com Barry

      Hi Scott, in the tutorial I create a products category taxonomy. So this is where all the categories go, it’s just specific to the post type. They can be nested so if you follow along you should be able to achieve what you need to.

      Since you’re making a e-commerce site and not a catalogue you might, instead, look into using a e-commerce plugin.

      Hope that helps, any follow up questions just ask :)

  • http://www.woony.be woony

    Hi,

    first thank you so much for this tutorial , learned a lot from this!

    I want to point out a thing that got me going wrong :)
    you say:
    “Create a file called taxonomy-products_categories.php.”
    it should be
    “Create a file called taxonomy-product_categories.php.”

    other than that I have a question. I made a little variation on your tutorial to learn. And how would I go about products that fit under more than one category.

    The problem with your example is, when I show the list from one of the 2 categories, the product is showed twice.

    For example make 2 categories: say A and B.
    and a product called P_1 and link it to both cat. A and B.

    When using your taxonomy list. you would get A & B clickable.
    Click A. get the category = A.
    you would get P_1, P_1. Instead of just P_1 once.

    Because of what is this? And how can I make this list distinct?
    Thanks in advance!

    • http://www.woony.be woony

      ok my above comment is incorrect!.
      It was because i didn’t have my timthumb set up. I saw 2 links for the same product , but the one was the alt link on the image… Never mind :)

      • http://barrywalsh-portfolio.com Barry

        No worries, glad you got it working!

  • Aneta

    Hi Barry and thanks for this wonderful tutorial. Exactly what I needed!

    But I’m having a mind blowing difficulty with the permalinks. They always lead me to 404 Page not found. What I did was to change register_taxonomy(‘product_categories’ to register_taxonomy(‘products’ and also changed it in all the other templates where it was referenced as ‘product_categories’ and ever since I started getting error 404.

    I want my permalinks to be structured like http://www.domain.com/products/ instead of http://www.domain.com/product_categories/. Is there any cure for that?

    Also I would like to have Products in the main menu and all the subcategories (taxonomies) to go in a drop down menu when I hover Products. But when I click Products, instead of showing me the top level taxonomies (as in this case Products itself would be top level), I would like for all products from all subcategories to show up. And as I click on the subcategories I’d like to just filter the products from that category. I’m intending to use a secondary left sided vertical navigation for the products pages which will serve as that filter.

    Any ideas and suggestions on how to achieve this? I can’t create a top level taxonomy Products and place all subcategories under it as when I’d click it the URL would be http://www.domain.com/products/products/.

    Thank you very much in advance!

    Best regards