Custom WooCommerce Product Variation Fields

This tutorials covers how to add custom WooCommerce Product Variation Fields, sanitise the inputs and display the fields against customers’ orders. We’ll do this with a few snippets of code, and without installing yet another plugin. It’s easy to extend with additional fields, and you don’t need to be a developer to follow it. I promise.

Example custom product variatoin field in WooCommerce
An example custom product-variation field in WooCommerce

How It’s Going to Work

We’re going to do three things in this tutorial:

  1. Inject some HTML to render a label and a textbox into the product-variation accordion control.
  2. Detect when a product-variation is saved, and pull the value of our custom field from $_POST.
  3. Hook the customer orders and add our product-variation field to the display (email and on-screen).

When we go to a WooCommerce > Edit Product page (for a Variable Product), we get all the HTML controls for the product-level things, but we also get repeater sub-forms (in an accordion) for each variation. If we add a custom form field to a product-variation sub-form, like this…

<!-- Example HTML fields -->
<label for="my_custom_field">Custom Note</label>
<input id="my_custom_field" name="my_custom_field" text="text" />

…we run into trouble as soon as there are two (or more) variations, because control IDs need to be unique. It’s OK if multiple HTML input fields have the same name, because they just come back to us in the POST data as an array. So we just need to be a bit careful when we’re laying out the HTML, and scanning the POST data.

Once we’ve stored the custom field values against the product variations using WordPress’ update_post_meta() function, it’s relatively easy to pick out and show the data against the orders.

Write the Code

In your custom WordPress child theme, create a new file called wpt-product-variation-fields.php and paste the following into it.


 * WP Tutorials : Custom product-variation fields for WooCommerce

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

const CUSTOM_META_VARIATION_NOTE = 'custom_product_note';

// More custom field names.
// const CUSTOM_META_ANOTHER_FIELD = 'another_custom_field';
// ...

 * Render the custom product-variation fields in the WooCommerce product editor.
 * Note: The $variation object is a WP_Post object.
function custom_render_variation_panel($loop, $variation_data, $variation) {
	echo '<p class="form-row">';
	$control_id = CUSTOM_META_VARIATION_NOTE . '_' . $loop;
		'<label for="%s">%s</label><input id="%s" name="%s[%s]" type="text" value="%s" class="widefat" />',
		esc_html('Custom Note'),
		esc_attr(get_post_meta($variation->ID, 'variation_' . CUSTOM_META_VARIATION_NOTE, true))
	echo '</p>';

	// Render more product-variation fields in here...
	// ...
	// ...
add_action('woocommerce_product_after_variable_attributes', 'custom_render_variation_panel', 10, 3);

 * When a product-variation is being saved, check to see if our fields are
 * present, sanitise them and save then as post_meta.
function custom_save_product_variation_options($variation_id, $variation_index) {
	$field_name = 'variation_' . CUSTOM_META_VARIATION_NOTE;
	if (array_key_exists($field_name, $_POST) && is_array($_POST[$field_name]) && array_key_exists($variation_index, $_POST[$field_name])) {

		// You might want to replace sanitize_text_field with sanitize_url(),
		// sanitize_email(), wp_kses_post(), intval(),  or some other way of
		// preventing hacky string injection.
		// sanitize_text_field() will work well in most cases.
		$field_value = sanitize_text_field($_POST[$field_name][$variation_index]);

		if (empty($field_value)) {
			// If the field value is empty, delete the post meta.
			delete_post_meta($variation_id, $field_name);
		} else {
			update_post_meta($variation_id, $field_name, $field_value);


	// Process more saved product-variation fields here...
	// ...
	// ...
add_action('woocommerce_save_product_variation', 'custom_save_product_variation_options', 10, 2);

 * When an order is being rendered (in an email or on the screen), catch each
 * order item and see if it's a product-variation that has any of our custom
 * fields.
function custom_woocommerce_order_item_meta_end($item_id, $item, $order) {
	if (empty($product = $item->get_product()) || !$product->is_type('variation')) {
		// This order item isn't a product-variation.
	} elseif (empty($variation_id = $product->get_variation_id())) {
		// ...
	} elseif (empty($custom_note = get_post_meta($variation_id, 'variation_' . CUSTOM_META_VARIATION_NOTE, true))) {
		// This product-variation doesn't have a custom note.
	} else {
			'<div class="custom-note">%s</div>',
add_action('woocommerce_order_item_meta_end', 'custom_woocommerce_order_item_meta_end', 10, 3);

Next up, go into your child theme’s functions.php and add the following couple of lines:

// WP Tutorials : Product-variation custom fields
require_once dirname(__FILE__) . '/wpt-product-variation-fields.php';

Save everything and edit a variable product in the back-end of your site. When you expand a product variation, you should see the new Custom Note field.

Right-click on the Custom Note’s text input field and inspect the element. Check how the field name comes through into the HTML field’s id:

	value="The blue one is the very best one, of course"
Product variation custom note field
Custom product-variation note
Custom product variataion note on a custom order
The custom product-variation note in the customer’s order

Test It and Tidy Up

Try making some purchases. We can’t just test the new code by itself – we need to make sure we haven’t broken the process for other types of products too.

  • Purchase a product variation that has a custom note set, and then buy a product variation that does not have a custom note.
  • Try different combinations in your cart, with simple products and product-variations.
  • Go to the front-end My Account page and look through your orders – maybe add a bit of styling to make the custom field(s) stand out.
  • Check the “New Order” emails to make sure the custom note comes through correctly.

That’s it – just a few lines of PHP and you can avoid having to install yet another plugin, with all the bloat that comes with it 😎 👍

Like This Tutorial?


Let us know

Leave a Comment

Your email address will not be published.