If you run your online shop using the excellent woocommerce plugin, built on top of the wordpress platform, and you want to set a custom (category specific) sort order for your products, sadly you are out of luck. The built in ‘sort products’ function works well if you dont mind having a global product sort order which remains the same across all categories, but if you want to have more fine-grained control over your sort order you need a little something extra. Having recently encountered this exact problem whilst working on some customisations for a client, and after 30 minutes of fruitless searching to find a pre-rolled solution, I thought it was time to roll up my sleeves instead. Some requirements for our solution;

  • It has to be ‘clean’ and not require changes to core files or the templates themselves to align with our coding practices.
  • It has to be executed in a way where we can enable/disable it and test it easily.
  • It has to be simple enough for the client to understand without needing any special training.
  • It has to allow custom ordering, but allow for any products which aren’t included to still appear in the category (after the ordered ones)

I am assuming that you already have WordPress and WooCommerce installed and are know your way around your active theme (or at the very least can edit functions.php within your theme directory), and have commented the code to explain what each part does;

Here are the steps involved to achieve custom product wort order nirvana;

  1. Download and install the excellent Advanced Custom Fields plugin
  2. Create a field group called something like ‘product order’ containing a single relationship field (can also be called ‘product order’). Fortunately the relationship field lets you drag and drop your objects in the order you want them, and will return an array of IDs in the order you set in the edit screen. Handy!
  3. Set ‘Filter by Post Type’ to ‘Product’
  4. Set ‘Return Format’ to return Post IDs
  5. Add a custom rule to your custom fields group to only appear if “Taxonomy term is equal to product_cat” to ensure that your new field group only appears on the product_cat term edit screen
  6. Add the following code to the bottom of your functions.php file within your theme (or somewhere more appropriate if you use a theme framework or some kind)

function my_custom_product_order($q)
{
	if(!is_admin())
	{
		// fetch current category id from active query
		$category_id = $q->get_queried_object_id();

		// get array of all product IDs in current category
		$product_ids = get_category_product_ids($category_id);

		// get preferred order from ACF field
		$product_ids_order_preferred = get_field('product_order', 'product_cat_' . $category_id);

		// if we have some product sort order set…
		if($product_ids_order_preferred)
		{
			// merge our preferred category ids array with the array of all products ids, and remove duplicates
			$product_ids = array_unique(array_merge($product_ids_order_preferred, $product_ids));
		}

		// set the 'posts__in' argument to the new array of post IDs (unfortunately wordpress doesn’t let you just pass an array of IDs straight in here)
		$q->set('post__in', $product_ids);

		// set the query orderby value to observe the posts__in field
		$q->set('orderby', 'post__in');
	}

	remove_action('woocommerce_product_query', 'custom_pre_get_posts_query');
}

add_action('woocommerce_product_query', ‘my_custom_product_order’);

and the following helper function to allow us to more easily fetch an array of all product IDs from within a specific category, which may come in handy for other things (stay DRY!)


// helper function to fetch all product IDs from a specific category ID

function get_category_product_ids($category_id)
{
	$args = array(
			'post_type' => 'product',
			'post_status' => 'publish',
			'fields' => 'ids',
			'posts_per_page' => -1,
			'tax_query' => array(
				array(
					'taxonomy' => 'product_cat',
					'field' => 'term_id',
					'terms' => $category_id,
					'operator' => 'IN'
				)
			)
	);

	$ids = get_posts($args);

	return $ids;
}

Hopefully this will be of some use to people looking to have more granular control over their product sort order on a per category basis. Let us know in the comments if you think we missed anything, or if we can improve it.