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.
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
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 😎 👍