Custom Product Meta

In this tutorial we are going to talk about how to add custom fields to WooCommerce products. I am not going to talk about plugins here – we will work with code snippets only and all the additional fields will be added to “Product Data” metabox.

Please also keep in mind that WooCommerce allows to hide or display specific product fields depending on a product type. So for example we can show our additional product fields only for “Downloadable” or “Variable” products.

Just like that:

WooCommerce custom product meta
As you can see we added a product field into “Advanced” tab, then we created a conditional “Misha” custom tab and added some additional custom product fields there as well.

Product Data Metabox Action Hooks (Default Tabs)

Just a reminder – if you add custom product meta using some plugins like ACF – shame on you! I mean why doing that when you can add beautiful and seamless product fields to a “Product data” metabox which is meant to be used just for that purpose. But if you’re looking for a custom metabox plugin, please use at least the ones without UI, for example my Simple Fields plugin by a developer for developers.

First things first let’s figure out what hooks are available for us for adding the fields. For each tab there are different hooks available.

General tab

Here we have 3 action hooks for each option group!

  • First option group with pricing options – woocommerce_product_options_pricing,
  • Second option group, downloadable product settings – woocommerce_product_options_downloads,
  • Third option group, tax settings – woocommerce_product_options_tax.

If you want to create you own option group below, use woocommerce_product_options_general_product_data, simple example:

add_action( 'woocommerce_product_options_general_product_data', 'misha_option_group' );
 
function misha_option_group() {
	echo '<div class="option_group">test</div>';
}

Note, that if you use one of the other 3 hooks, your fields shouldn’t be wrapped into <div class="option_group"> element.

woocommerce_product_options_general_product_data

Inventory tab

  • First group, under stock status settings – woocommerce_product_options_stock_status,
  • Second one, woocommerce_product_options_sold_individually.

Or add your custom groups with woocommerce_product_options_inventory_product_data.

Shipping tab

  • First group with product dimension – woocommerce_product_options_dimensions,
  • Second group – woocommerce_product_options_shipping.

Here is no hook for your custom options group 😱 but if you absolutely need it, you can do some tricks with the second hook and closing </div> element.

Linked products tab

Just one hook is here – woocommerce_product_options_related.

Advanced tab

Two hooks are here – woocommerce_product_options_reviews for the reviews option group and woocommerce_product_options_advanced for those who want to create an own option group.

Add Custom Field to WooCommerce Product “Advanced” tab

If you do not know where to insert the code, please check this.

add_action( 'woocommerce_product_options_advanced', 'rudr_product_field' );
function rudr_product_field(){

	echo '<div class="options_group">';
	woocommerce_wp_checkbox(
		array(
			'id'      => 'spr',
			'value'   => get_post_meta( get_the_ID(), 'super_product', true ),
			'label'   => 'This is a super product',
			'desc_tip' => true,
			'description' => 'If it is not a regular WooCommerce product',
		)
	);
	echo '</div>';

}

add_action( 'woocommerce_process_product_meta', 'rudr_save_field' );
function rudr_save_field( $id ){

	$super = isset( $_POST[ 'spr' ] ) && 'yes' === $_POST[ 'spr' ] ? 'yes' : 'no';
	update_post_meta( $id, 'super_product', $super );

}

The code is quite simple but here are some notes about it:

  • Please note that I am using woocommerce_wp_checkbox() function. It is a standard WooCommerce function that should be used when it is needed to add additional product fields to “Product Data” metabox. So you do not have to use just HTML. The same applies for other type of fields – we use woocommerce_wp_text_input() function to create a simple text field, woocommerce_wp_textarea_input() – for textarea fields and woocommerce_wp_select() for select dropdowns. Some examples of using these functions you can also find here.
  • When you use woocommerce_wp_checkbox() for checkboxes, please note, that there are two checkbox value – yes if it is checked and empty if unchecked.
  • desc_tip is a very interesting parameter as well – if set to true, the value of description will be displayed like a tip, in another case it will printed near the field.
  • It is so awesome that WooCommerce allows to process product metadata with woocommerce_process_product_meta action hook (not with the standard save_post) because in this case we do not have to do some conditional checks (nonces etc). But you have to always remember about sanitizing.

Here we are:

WooCommerce product add custom field

Create a Custom Tab and Add Custom Field in it

Before just copying the code below, please read this:

  • You can unset any of the default tabs (line 4), using the ids general, shipping, linked_product, attribute, variations, advanced, but I’m not sure if it is a good idea to do.
  • Awesome feature that allows you to show or hide tabs or sections or single option fields for specific product types! (line 9 or 44). Just use some of these classes show_if_simple, show_if_grouped, show_if_external, show_if_variable, show_if_virtual, show_if_downloadable.
  • Use the priority parameter (line 10) if you would like your tab to be displayed at the specific position.
  • In the below example there is just one option group but you can use more groups.
add_filter( 'woocommerce_product_data_tabs', 'rudr_product_settings_tabs' );
function rudr_product_settings_tabs( $tabs ){

	//unset( $tabs[ 'inventory' ] );

	$tabs[ 'misha' ] = array(
		'label'    => 'Misha',
		'target'   => 'misha_product_data',
		'class'    => array( 'show_if_virtual' ),
		'priority' => 21,
	);
	return $tabs;

}

add_action( 'woocommerce_product_data_panels', 'rudr_product_panels' );
function rudr_product_panels(){

	echo '<div id="misha_product_data" class="panel woocommerce_options_panel hidden">';

	woocommerce_wp_text_input(
		array(
			'id'          => 'plg_ver',
			'value'       => get_post_meta( get_the_ID(), 'plg_ver', true ),
			'label'       => 'Plugin version',
			'description' => 'Description when desc_tip param is not true'
		)
	);

	woocommerce_wp_textarea_input(
		array(
			'id'          => 'plg_changes',
			'value'       => get_post_meta( get_the_ID(), 'plg_changes', true ),
			'label'       => 'Changelog',
			'desc_tip'    => true,
			'description' => 'Prove the plugin changelog here',
		)
	);

	woocommerce_wp_select(
		array(
			'id'            => 'plg_ext',
			'value'         => get_post_meta( get_the_ID(), 'plg_ext', true ),
			'wrapper_class' => 'show_if_downloadable',
			'label'         => 'File extension',
			'options'       => array(
				'' => 'Please select',
				'zip' => 'Zip',
				'gzip' => 'Gzip'
			),
		)
	);

	echo '</div>';

}


add_action( 'woocommerce_process_product_meta', 'rudr_save_fields' );
function rudr_save_fields( $id ){

	update_post_meta( $id, 'plg_ver', sanitize_text_field( $_POST[ 'plg_ver' ] ) );

	update_post_meta( $id, 'plg_changes', sanitize_textarea_field( $_POST[ 'plg_changes' ] ) );

	$plg_ext = isset( $_POST[ 'plg_ext' ] ) && in_array( $_POST[ 'plg_ext' ], array( 'zip', 'gzip' ) ) ? $_POST[ 'plg_ext' ] : '';
	update_post_meta( $id, 'plg_ext', $plg_ext );

}

The result on the screenshot.

custom tab with additional product fields

Custom icon for a product data metabox tab

In the above example I am using the default icon, but it is possible to use any icon from Dashicons set.

All you need to do is to select an icon you like and then click “Copy CSS”, then just paste it like this:

#woocommerce-product-data ul.wc-tabs li.misha_options.misha_tab a:before{
	content: "\f487";
}

You can add this CSS snippet in admin_head hook or into a .css file. Ta-dam:

custom tab icon

Using WC_Product object methods to get and add product meta

In the examples in this tutorial I primarily used get_post_meta() and update_post_meta() functions which is totally ok, but maybe for some reasons you decide to switch to get_meta() and update_meta() methods of WC_Product object. How to do that?

This is how to get product meta:

function rudr_product_panels(){

	$product = wc_get_product( get_the_ID() );

	echo '<div id="misha_product_data" class="panel woocommerce_options_panel hidden">';

	woocommerce_wp_text_input(
			array(
			'id'                => 'plg_ver',
			'value'             => $product->get_meta( 'plg_ver' ),

This is how you can update product meta:

add_action( 'woocommerce_process_product_meta', 'rudr_save_fields' );
function rudr_save_fields( $id ){

	$product = wc_get_product( $id );

	$product->update_meta_data( 'plg_ver', sanitize_text_field( $_POST[ 'plg_ver' ] ) );
	
	$product->update_meta_data( 'plg_changes', sanitize_textarea_field( $_POST[ 'plg_changes' ] ) );
	
	$plg_ext = isset( $_POST[ 'plg_ext' ] ) && in_array( $_POST[ 'plg_ext' ], array( 'zip', 'gzip' ) ) ? $_POST[ 'plg_ext' ] : '';
	$product->update_meta_data( 'plg_ext', $plg_ext );
	
	// save changes into the database
	$product->save_meta_data();

As you can see we can retrieve WC_Product object only using wc_get_product() function, because it is neither available as a global variable nor as an additional hook argument.

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