How to Add Custom Fields to Products in WooCommerce
In this tutorial, I will show you two ways of adding custom fields to products programmatically in WooCommerce – in every example, we will do it completely from scratch and with the help of my Simple Fields plugin.
Another interesting feature I am about to show you in this guide is how to show or hide specific product custom fields depending on a product type. For example, we can show our custom product fields for “Downloadable” or “Variable” products only.
Just like that:

Product Data Metabox Action Hooks (Default Tabs)
First things first, I’d like to discuss different action hooks that are available in WooCommerce. The hook you choose for your custom fields defines the custom fields’ location in the “Product Data” metabox.
For every tab, there is a bunch of hooks available. It means that we have a lot of options!
General tab
Here we have 3 action hooks for each option group!
| Hook | Description |
|---|---|
woocommerce_product_options_pricing | Allows adding fields into the downloadable product settings group, specifically after the “Download expiry” custom field. |
woocommerce_product_options_downloads | Allows adding fields into the downloadable product settings group, specifically after the “Download expiry” custom field. |
woocommerce_product_options_tax | The fields will be added to the tax settings field group, after the “Tax class” field. |
There is another thing, the woocommerce_product_options_general_product_data action hook, which allows you to create your custom option group with a bunch of custom fields inside.
Here is an example of how it works:
<?php
add_action( 'woocommerce_product_options_general_product_data', function() {
?><div class="option_group">test</div><?php
} );Note that if you use one of the other 3 hooks mentioned in the table, your fields shouldn’t be wrapped into the <div class="option_group"> element.

Inventory tab
The hooks available for us in the “Inventory” tab are the following:
| Hook | Description |
|---|---|
woocommerce_product_options_stock_status | Allows us to add custom fields under the stock status settings. |
woocommerce_product_options_sold_individually | The second field group contains only one field – “Sold individually”, and with this hook, you can add more custom fields to this group. |
Or add your custom field groups with woocommerce_product_options_inventory_product_data. By the way, this hook I am using in my Inventory Sync for WooCommerce plugin to add a checkbox that allows us to exclude a specific product from the sync.
Shipping tab
| Hook | Description |
|---|---|
woocommerce_product_options_dimensions | Allows us to add custom fields to the product dimensions group. |
woocommerce_product_options_shipping | It allows us to add custom fields right after the “Shipping class” field. |
In case you’d like to add a custom field group to the “Shipping” tab, you can make use of the following action hook: woocommerce_product_options_shipping_product_data.
Linked products tab
Just one hook is here, which is woocommerce_product_options_related, and it allows us to create a custom option group only.
Advanced tab
| Hook | Description |
|---|---|
woocommerce_product_options_reviews | With this hook, you can add product custom fields into the reviews option group, right after the “Enable reviews” field. |
For custom option groups in the “Advanced” tab – woocommerce_product_options_advanced.
Example 1. Add a Custom Field to the Product “Advanced” tab
This is the custom field we are going to add in this example:

As promised, I will show you two code snippets. By the way, if you do not know where to insert the code, please check this.
In the first code snippet, I am going to add a custom field to a product with the help of my Simple Fields plugin. Here it is:
/**
* @snippet: WooCommerce Add Custom Field to the "Advanced" tab with Simple Fields
* @author: Misha Rudrastyh
* @url: https://rudrastyh.com/woocommerce/product-data-metabox.html#example-1
*/
add_filter( 'simple_register_product_fields', 'rudr_product_field' );
function rudr_product_field( $option_groups ) {
$option_groups[] = array(
'tab' => 'advanced', // general, inventory, shipping, linked_products
'fields' => array(
array(
'id' => 'checkbox_test',
'label' => 'Checkbox',
'type' => 'checkbox',
'label' => 'This is a super product',
'description' => 'If it is not a regular WooCommerce product',
),
)
);
return $option_groups;
}Below is the way of doing it completely from scratch:
<?php
/**
* @snippet: WooCommerce Add Custom Field to the Product "Advanced" tab
* @author: Misha Rudrastyh
* @url: https://rudrastyh.com/woocommerce/product-data-metabox.html#example-1
*/
add_action( 'woocommerce_product_options_advanced', 'rudr_product_field' );
function rudr_product_field(){
?><div class="options_group"><?php
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',
)
);
?></div><?php
}
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 );
}If you compare these two snippets, both are quite simple because we added only one checkbox field. However, if you need to add a lot of fields, and specifically complex fields like image and gallery fields, repeaters, etc., then I’d definitely recommend you take a look at the Simple Fields plugin.
Please note that I am using the woocommerce_wp_checkbox() function here. It is a standard WooCommerce function that should be used when it is needed to add additional product fields to the “Product Data” metabox. So you shouldn’t use just HTML. The same applies to other types of fields:
| Function | Description |
|---|---|
woocommerce_wp_text_input() | Text field |
woocommerce_wp_textarea_input() | Textarea field |
woocommerce_wp_select() | Select dropdown field |
woocommerce_wp_checkbox() | Checkbox field |
woocommerce_wp_hidden_input() | Hidden field |
woocommerce_wp_radio() | Radio buttons |
A couple of notes about the parameters of the woocommerce_wp_checkbox() function:
- There are two checkbox values –
yes(if checked) and empty (if unchecked). - The
desc_tipparameter is a very interesting one as well – if set totrue, the value ofdescriptionwill be displayed like a tip; in another case, it will be printed near the field.
It is so awesome that WooCommerce allows us to process product metadata with the woocommerce_process_product_meta action hook (not with the standard save_post) because in this case, we do not have to do conditional checks (nonces, etc). But you have to always remember to sanitize.
Example 2. Create a Custom Tab and Add a Custom Field to It

Before copying the code:
- An awesome feature that allows you to show or hide tabs or sections, or single custom fields for specific product types – 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
priorityparameter if you would like your tab to be displayed at a specific position.
And again, let me show you how to do that with Simple Fields first:
/**
* @snippet: Add Custom Fields to Products Programmatically with Simple Fields
* @author: Misha Rudrastyh
* @url: https://rudrastyh.com/woocommerce/product-data-metabox.html#product_metabox_custom_tab
*/
add_filter( 'simple_register_product_fields', 'rudr_product_settings_tabs' );
function rudr_product_settings_tabs( $option_groups ) {
$option_groups[] = array(
'tab' => array(
'id' => 'misha_product_data',
'label' => 'Misha',
'class' => array( 'show_if_virtual' ),
'priority' => 21,
),
'fields' => array(
array(
'id' => 'plg_ver',
'label' => 'Plugin version',
'description' => 'Description when desc_tip param is not true',
),
array(
'id' => 'plg_changes',
'type' => 'textarea',
'label' => 'Changelog',
'description' => 'Put the plugin changelog here.',
),
array(
'id' => 'plg_ext',
'label' => 'File extension',
'type' => 'select',
'wrapper_class' => array( 'show_if_downloadable' ),
'placeholder' => 'Please select',
'options' => array(
'zip' => 'Zip',
'gzip' => 'Gzip'
),
),
)
);
return $option_groups;
}And a completely from scratch solution:
/**
* @snippet: WooCommerce Add Custom Fields to Products Programmatically
* @author: Misha Rudrastyh
* @url: https://rudrastyh.com/woocommerce/product-data-metabox.html#product_metabox_custom_tab
*/
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' => 'Put 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 );
}You can also unset any of the default tabs (line 9), using their IDs general, shipping, linked_product, attribute, variations, advanced, but I’m not sure if it is a good idea to do.
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 the Dashicons set.
All you need to do is 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 to the admin_head hook or into a .css file. Ta-dam:

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 ok, but maybe for some reason 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 the WC_Product object only by using the wc_get_product() function, because it is neither available as a global variable nor as an additional hook argument.
Misha Rudrastyh
Hey guys and welcome to my website. For more than 15 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
Very useful information!
Thank you for sharing!
this is great but how do i get these values passed into my orders
You need this tutorial.
is this possible to call product data meta box to another post type
Looks tricky 🙃
I didn’t try that.
If that could be possible. It can be wonderful. Thank you.
hi Misha.
This info was very helpful thank you!
i have questions :
How to hide plugins folder in (wp-content)? Is hidden for your site!
I want to close access via browser (inspect element -> Sources).
plz help me Misha.
thanks you kindly
Hi,
Not sure, if I understand the question, but I think an empty
index.phpfile placed insidewp-content/pluginsshould help.Hi,
How I can hide my wp folders from Inspect Element (Developer Tools)?
When I open Inspect Element (Developer Tools) on my site, under Source tab, my WP folders are located such as wp-content, and wp-include. I want to hide all these folders for security reasons.
Great article! Any chance you know if you can use a product selection field, similar to the ‘Linked Products’ tab where the upsells and cross-sells has a field that lets you choose products?
Thank you, Alex!
Please, make a look at this tutorial. Hope it helps.
Sir, I am your big fan❤️.
Your posts helped me a lot.
Hi Misha, thanks for the tutorial, it’s incredibly helpful, as always.
I’m wondering if it’s possible to have the visibility of a custom field be dependent on the values of other fields and to have this update dynamically, not just when the page is saved. Would you be able to give any advice or tips on that?
I can have the conditional logic in the function that creates the new field, of course, but that relies on updating the page/product to save and then pick up any changes.
Hi Krakow,
Thank you for your question. It is possible, of course. But, it also depends on which way you would like to achieve that? The easiest is this one.
Thanks very much Misha.
After commenting, I was able to get it working using the admin_enqueue_scripts hook targeted at product edit pages to add a little javascript to do what I needed, but the approach you mention looks like it should be neater, so I’ll try that instead now.
Thanks again – so much useful information on your blog.