Add a products column to the WooCommerce admin orders page

Custom WooCommerce admin column tutorial

In this short tutorial, we’ll create a new column on the WooCommerce admin orders page so you can see the products (line items) in each order. We’ll hook a WordPress filter to modify the list of columns available to the table. Then we’ll hook an action so we can render the contents of the column on a per-order basis. We’ll also add a little bit of styling to the admin page so it looks good on-screen.

A custom column on the WooCommerce orders admin table
Custom column on the WooCommerce admin orders table

importantBefore starting, make sure you’re using a custom child theme so you can edit “functions.php”.

How it will work

When adding custom columns to WordPress admin post tables, the tricky thing is figuring out the names of the filter & action to use. The names are dynamic… based on the post type you’re interacting with.

Because we’re altering the structure of the table for the “shop_order” post type, we’re going to use manage_edit-shop_order_columns (filter) to create the custom admin column, and manage_shop_order_posts_custom_column (action) to render it on a per-row basis.

It’s easy to add the Products column to the end of the list, but it would make more sense if we could inject it after the Order Status (order_status) column. To do this, we need to take the $columns array, find index of the “order_status” column and split $columns into two smaller arrays at this point. Then we append our column to the first array, and append the second array after that. We’ve wrapped this in a function called wptwcopc_insert_into_array_after_key(), which is a handy thing to keep in your toolbox for future projects.

Featured plugin

WooCommerce Flexible Product Tabs
Elegant and flexible product tabs for WooCommerce. Use the Block Editor for your main Description tab too.
Flexible Product Tabs for WooCommerce

The PHP code to modify the admin orders table

In your child theme, create a new file called wpt-wc-order-products-column.php and paste the following into it:

<?php

/**
 * Headwall WP Tutorials WooCommerce Order Products Column (WPTWCOPC)
 *
 * https://wp-tutorials.tech/add-functionality/add-a-products-column-to-woocommerce-admin-orders-page/
 *
 */

defined('WPINC') || die();

const WPTWCOPC_NAME = 'wptwcopc';
const WPTWCOPC_VERSION = '1.0.0';
const WPTWCOPC_PRODUCTS_COLUMN_NAME = 'order_products';

/**
 * A handy utility function to insert a key/value pair into an associative array.
 *
 * @param array  $source_array The array you want to inject something into.
 * @param string $key          The key of the element you want to inject your
 *                             new data after.
 * @param array  $new_element  The associative array you want to inject.
 *
 * @return array               The original $source_array with $new_element
 *                             injected into it, after $key.
 */
function wptwcopc_insert_into_array_after_key(array $source_array, string $key, array $new_element) {
	if (array_key_exists($key, $source_array)) {
		$position = array_search($key, array_keys($source_array)) + 1;
	} else {
		$position = count($source_array);
	}

	$before = array_slice($source_array, 0, $position, true);
	$after = array_slice($source_array, $position, null, true);

	return array_merge($before, $new_element, $after);
}

/**
 * Called by WordPress when it needs to know the columns for the Shop Order
 * admin table.
 *
 * @param array  $columns  Associative array of columns names and titles.
 *
 * @return array           Same as $columns, but with our new column injected
 *                         into it.
 *
 */
function wptwcopc_shop_order_columns($columns) {
	$columns = wptwcopc_insert_into_array_after_key(
		$columns,
		'order_status',
		array(
			WPTWCOPC_PRODUCTS_COLUMN_NAME => 'Items',
		)
	);

	return $columns;
}
add_filter('manage_edit-shop_order_columns', 'wptwcopc_shop_order_columns');

/**
 * Called by WordPress when it needs to know the columns for the Shop Order
 * admin table.
 *
 * @param string  $column   The name of the column being rendered.
 * @param int     $post_id  ID of the current post (shop_order).
 *
 */
function wptwcopc_shop_order_custom_column($column, $post_id) {
	if ($column != WPTWCOPC_PRODUCTS_COLUMN_NAME) {
		// The column being rendered is not ours, so we don't need to do anything.
	} elseif (!is_a($wc_order = wc_get_order($post_id), 'WC_Order')) {
		// We should never end up in here, but wc_get_order() is able to bool
		// (false) so we need to be able to handle it.
	} elseif (empty($items = $wc_order->get_items())) {
		// This order has no line itmes. Perhaps it only has fees on it?
	} else {
		echo '<ul class="order-products">';
		foreach ($items as $item) {
			printf(
				'<li>%s</li>',
				esc_html($item->get_name())
			);
		}
		echo '</ul>'; // .order-products
	}
}
add_action('manage_shop_order_posts_custom_column', 'wptwcopc_shop_order_custom_column', 10, 2);

/**
 * Enqueue a bit of CSS to tidy up our admin column.
 */
function wptwcopc_enqueue_scripts() {
	$theme_version = wp_get_theme()->get('Version');
	$base_uri = get_stylesheet_directory_uri();
	$handle = WPTWCOPC_NAME;

	wp_enqueue_style(
		$handle,
		$base_uri . '/wpt-wc-order-products-column.css',
		null, // We don't depend on any other admin styles
		WPTWCOPC_VERSION
	);
}
add_action('admin_enqueue_scripts', 'wptwcopc_enqueue_scripts', 10, 1);

Then open your child theme’s functions.php and add the following couple of lines:

// WooCommerce Order Products Column
require_once dirname(__FILE__) . '/wpt-wc-order-products-column.php';

Looking at our main PHP file, you’ll see that wptwcopc_shop_order_columns() takes an array of columns that you can modify and then return. The incoming array is just a set of key/value pairs like this:

'cb'               => '<input type="checkbox" />'
'order_number'     => 'Order'
'order_date'       => 'Date'
'order_status'     => 'Status'
'billing_address'  => 'Billing'
'shipping_address' => 'Ship to'
'order_total'      => 'Total'
'wc_actions'       => 'Actions'

The key is the internal name of each column, the value is the on-screen text (which can vary, based on the display language).

We want to inject our new column…

'order_products'   => 'Items'

…into the array after the “order_status” item and return the modified array.

When WordPress renders the table, it loops through each order one-by-one. For each order, it then loops through and renders each column. This is when wptwcopc_shop_order_columns() gets called. We need to check which custom column is being rendered and, if it’s “order_products”, we get the order’s line items and render them as an unordered list.

Some minor styling

To make the column a bit tidier, we also enqueue a small (admin-only) CSS file using the admin_enqueue_scripts action. In your custom child theme, create a file called wpt-wc-order-products-column.css and paste the following into it:

/**
 * Headwall WP Tutorials WooCommerce Order Products Column (WPTWCOPC)
 *
 * https://wp-tutorials.tech/add-functionality/add-a-products-column-to-woocommerce-admin-orders-page/
 *
 */

.column-order_products .order-products {
	font-size: 80%;
	margin: 0;
}

.column-order_products .order-products li {
	display: block;
	margin: 0;
}

.column-order_products .order-products li:not(:first-child) {
	margin-top: 0.2em;
}

When you’ve saved all that, go to WooCommerce > Orders and you should see your new column in there, with each order’s items laid out nicely.

Extending the code

If you want to add more custom columns to the admin table, it’s pretty easy. You could do something like this in wptwcopc_shop_order_columns():

const WPTWCOPC_ANOTHER_ONE_COLUMN_NAME = 'another_col_one';
const WPTWCOPC_ANOTHER_TWO_COLUMN_NAME = 'another_col_two';

function wptwcopc_shop_order_columns($columns) {
	$columns = wptwcopc_insert_into_array_after_key(
		$columns,
		'order_status',
		array(
			WPTWCOPC_PRODUCTS_COLUMN_NAME => 'Items',
			WPTWCOPC_ANOTHER_ONE_COLUMN_NAME => 'Another One',
			WPTWCOPC_ANOTHER_TWO_COLUMN_NAME => 'Another Two',
		)
	);

	return $columns;
}

Obviously, you should swap out WPTWCOPC_ANOTHER_ONE_COLUMN_NAME and WPTWCOPC_ANOTHER_ONE_COLUMN_NAME for something more meaningful to your project.

Then you just need to add some handlers for “$column == WPTWCOPC_ANOTHER_ONE_COLUMN_NAME” in wptwcopc_shop_order_custom_column(), and you can render whatever you want in there.

That’s all there is to it… custom admin columns in the WooCommerce orders table 😎 👍

Like This Tutorial?

Let us know

WordPress plugins for developers

Leave a comment