Columns in WooCommerce

In this in-depth tutorial I am going to guide you through the process of managing columns in order or product tables etc in WooCommerce admin which is very similar to managing columns in any WordPress admin pages but there are some moments you need to keep in mind for sure.

Also we’re going to take a look at different examples, for example we’re going to add a column to the product list with total sales like this:

WooCommerce add column to the product list
We added a sortable “total sales” column to the product list in WooCommerce admin. You can jump straight to this example.

Which Column Hooks to Use?

Depending on what WooCommerce admin page (orders list, products list etc) you’re going to create a custom column, the process might be slightly different, at least the hooks you’re using are going to be different. And right now I am going to show you what hooks we can use. You can also jump straight to the examples.

There are also different hooks for adding columns and populating columns with data, right now we are about to take a look at each of them step by step.

Adding columns

First things first – we need just to add an empty column to the admin table. Below is the list of hooks intended to help you with that.

HookAdmin page
manage_edit-shop_order_columnsOrders as CPT
manage_woocommerce_page_wc-orders_columnsOrders with HPOS
manage_edit-product_cat_columnsProduct categories
manage_edit-product_tag_columnsProduct tags

I would like to additionally highlight the thing with WooCommerce orders – most likely you’re using the latest WooCommerce version with HPOS enabled by default, which means that WooCommerce orders aren’t a custom post type (CPT) anymore and the hook manage_edit-shop_order_columns not working for you. Don’t worry, just use manage_woocommerce_page_wc-orders_columns instead and check the example below where we create a WooCommerce column for orders which works for both HPOS-based and CPT-based orders.

Now let’s take a look at a simple example. No matter what hook you’re using, the code is going to look similar – we just add one more item into the $columns array which is the associative array of column IDs and titles like this array( 'column_id' => 'column_title' ).

// that's the hook
add_filter( 'manage_woocommerce_page_wc-orders_columns', 'rudr_new_column' );
function rudr_new_column( $columns ){
	// just add a new column here
	$columns[ 'rudr_new_column' ] = 'My new column';
	// return the modified array
	return $columns;

That’s it. The column is added. These set of hooks is also useful when you need to remove a specific column.

Oh, yes, if you don’t know where to insert this code, please read this.

Populating columns with data

Once the column is added we need to display some data in it, right? There is also a different set of hooks for that.

HookAdmin page
manage_posts_custom_columnProducts and CPT-based orders
manage_woocommerce_page_wc-orders_custom_columnHPOS-based orders
manage_product_cat_custom_columnProduct categories
manage_product_tag_custom_columnProduct tags

The callback function for these hooks isn’t the same, for example for products and orders we print the result inside the function, but for users, tags and categories – return the result.

Users, product categories and product tags:

// for Users
// add_filter( 'manage_users_custom_column', 'misha_populate_columns', 10, 3 );
// for product categories
// add_filter( 'manage_product_cat_custom_column', 'misha_populate_columns', 10, 3 );
// for product tags
add_filter( 'manage_product_tag_custom_column', 'misha_populate_columns', 10, 3 );

function misha_populate_columns( $output, $column_name, $object_id ) {
	// $object_id is either the user ID or product category/tag ID

	if( 'column ID here' === $column_name ) { // you can use switch()
		// do something and write the result into $output
		$output .= 'some column data here';
	return $output;

Products and CPT-based orders:

// products and legacy orders (CPT-based)
add_action( 'manage_posts_custom_column', 'misha_populate_columns' );

function misha_populate_columns( $column_name ){
	global $post, $the_order;
	if( 'column ID here' === $column_name ) {
		// do something and print the result
		echo 'some column data here';


HPOS-based orders:

add_action( 'manage_woocommerce_page_wc-orders_custom_column', 'misha_populate_orders_column', 25, 2 );

function misha_populate_orders_column( $column_name, $order ){ // WC_Order object is available as $order variable here

	if( 'column ID here' === $column_name ) {
		// do something and print the result
		echo 'some column data here';


Now we’ve finished with the theory part and you can definitely jump straight to the examples.

How to Remove Any Column?

Sometimes your admin pages can be overloaded with the columns and you would like to clean up the things a little bit and remove some of the columns you don’t need. But of course you can also hide them in “Screen Options” – don’t forget about that.

Here is step by step:

  1. Decide on what admin page you would like to remove columns, then go to this chapter of this tutorial and choose an appropriate hook.
  2. Find out the column ID, in order to do so you can just “Inspect” the column in your browser.
  3. Finally, the code:
// replace 'manage_edit-product_columns' hook with the hook you need
add_filter( 'manage_edit-product_columns', 'misha_remove_woo_columns' );
function misha_remove_woo_columns( $columns ) {
	unset( $columns[ 'column ID here' ] );
	// for example unset( $columns[ 'product_cat' ] );
	return $columns;


You can remove multiple columns at the same time of course.

How to Create a Custom Column (Examples)

Example 1. Add column to order list

I decided to start with this example because the hook manage_edit-shop_order_columns not working anymore if you’re using the latest WooCommerce version or at least have HPOS turned on in settings.

As an example let’s just print the products purchases near every order like this:

WooCommerce add a column to the order list in admin

The code below works great for both legacy and HPOS-based orders:

// legacy – for CPT-based orders
add_filter( 'manage_edit-shop_order_columns', 'misha_order_items_column' );
// for HPOS-based orders
add_filter( 'manage_woocommerce_page_wc-orders_columns', 'misha_order_items_column' );

function misha_order_items_column( $columns ) {

	// let's add our column before "Total"
	$columns = array_slice( $columns, 0, 4, true ) // 4 columns before
	+ array( 'order_products' => 'Purchased products' ) // our column is going to be 5th
	+ array_slice( $columns, 4, NULL, true );

	return $columns;


// legacy – for CPT-based orders
add_action( 'manage_shop_order_posts_custom_column', 'misha_populate_order_items_column', 25, 2 );
// for HPOS-based orders
add_action( 'manage_woocommerce_page_wc-orders_custom_column', 'misha_populate_order_items_column', 25, 2 );
function misha_populate_order_items_column( $column_name, $order_or_order_id ) {

	// legacy CPT-based order compatibility
	$order = $order_or_order_id instanceof WC_Order ? $order_or_order_id : wc_get_order( $order_or_order_id );

	if( 'order_products' === $column_name ) {

		$items = $order->get_items();
		if( ! is_wp_error( $items ) ) {
			foreach( $items as $item ) {
 				echo $item[ 'quantity' ] .' × <a href="' . get_edit_post_link( $item[ 'product_id' ] ) . '">'. $item[ 'name' ] .'</a><br />';
				// you can also use $order_item->variation_id parameter
				// by the way, $item[ 'name' ] will display variation name too



Take a look at 24 line, I find it very interesting, because hooks manage_shop_order_posts_custom_column and manage_woocommerce_page_wc-orders_custom_column have different second argument – it is either an order ID or an order object accordingly and we’re using $order_or_order_id instanceof WC_Order condition to check that.

Example 2. Add column to product list

The idea here is that WooCommerce creates a custom meta field total_sales for each product and stores the number of total sales in it. It allows us to use this value inside pre_get_posts hook and create a sortable column!

Here is the result what we are going to create:

WooCommerce add column to the product list
As you can see the title of “total sales” column is clickable, which means – sortable.

And here is the code for your functions.php:

// add
add_filter( 'manage_edit-product_columns', 'rudr_add_product_list_column' );
function rudr_add_product_list_column( $column_name ) {

	// a little different way of adding new columns
	return wp_parse_args(
			'total_sales' => 'Total Sales'


// populate
add_action( 'manage_posts_custom_column', 'rudr_populate_product_column', 25, 2 );
function rudr_populate_product_column( $column_name, $product_id ) {

	if( 'total_sales' === $column_name ) {
		echo get_post_meta( $product_id, 'total_sales', true );


// make sortable
add_filter( 'manage_edit-product_sortable_columns', 'rudr_sortable_column' );
function rudr_sortable_column( $sortable_columns ) {

	return wp_parse_args(
			'total_sales' => 'by_total_sales' // column name => sortable arg


// doing to sorting stuff
add_action( 'pre_get_posts', function( $query ) {

	if( ! is_admin() || empty( $_GET[ 'orderby' ] ) || empty( $_GET[ 'order' ] ) ) {
		return $query;

	if( 'by_total_sales' === $_GET[ 'orderby' ] ) {
		$query->set( 'meta_key', 'total_sales' );
		$query->set( 'orderby', 'meta_value_num' );
		$query->set( 'order', $_GET[ 'order' ] );

	return $query;

} );

Also the question here is that can we really use the product meta data as a regular posts meta data? I mean using get_post_meta(), pre_get_posts hook etc. Because if you have ever done that to orders, well, now for HPOS-orders your code doesn’t work anymore. And I think – yes, we can do that for now because, in my opinion, using CPT for WooCommerce products is exactly what it is meant for. No doubt maybe one day we will face to “high performance product storage”, but I haven’t heard about it at all.

Example 3. Add a column with WooCommerce billing details to users admin page

The last but not least let’s add one more column to Users > All users page this time. In this column we’re going to display billing addresses. Just like this:

billing address column in users table

And here is ready to use code:

add_filter( 'manage_users_columns', 'rudr_billing_address_column' );
function rudr_billing_address_column( $columns ) {

	return array_slice( $columns, 0, 3, true ) // 3 columns before
	+ array( 'billing_address' => 'Billing Address' ) // our column is 4th
	+ array_slice( $columns, 3, NULL, true );


add_filter( 'manage_users_custom_column', 'rudr_populate_address', 10, 3 );
function rudr_populate_address( $row_output, $column_name, $id ) {

	if( 'billing_address' === $column_name ) {

		$address = array();
		if( $address_1 = get_user_meta( $id, 'billing_address_1', true ) ) {
			$address[] = $address_1;
		if( $address_2 = get_user_meta( $id, 'billing_address_2', true ) ) {
			$address[] = $address_2;
		if( $city = get_user_meta( $id, 'billing_city', true ) ) {
			$address[] = $city;
		if( $postcode = get_user_meta( $id, 'billing_postcode', true ) ) {
			$address[] = $postcode;
		if( $country = get_user_meta( $id, 'billing_country', true ) ) {
			$address[] = $country;

		$row_output = join( ', ', $address );
	return $row_output;

Misha Rudrastyh

Misha Rudrastyh

Hey guys and welcome to my website. For more than 10 years I've been doing my best to share with you some superb WordPress guides and tips for free.

Need some developer help? Contact me

Follow me on X