WooCommerce product variations have their own meta data, separate from the parent product. Adding a custom field to each variation — a per-variation weight note, a custom SKU prefix, or an upsell badge — requires hooks on three levels: rendering the field, saving its value, and displaying it on the front end.
Problem: How do you add an extra field — such as a custom note, an engraving option, or a delivery date — to individual WooCommerce product variations and carry the value through the cart and into the order?
Solution: Use woocommerce_variation_options_pricing to output the field in the variation panel, woocommerce_save_product_variation to save it as variation meta, and woocommerce_add_cart_item_data + woocommerce_get_item_data to pass the value through the cart into the order line item.
// 1. Add the field to the variation form in the admin
add_action( 'woocommerce_variation_options_pricing', 'add_variation_badge_field', 10, 3 );
function add_variation_badge_field( $loop, $variation_data, $variation ) {
woocommerce_wp_text_input( [
'id' => "variation_badge_{$loop}",
'name' => "variation_badge[{$loop}]",
'value' => get_post_meta( $variation->ID, '_variation_badge', true ),
'label' => __( 'Badge text', 'textdomain' ),
'description' => __( 'e.g. "Best Value", "New"', 'textdomain' ),
'desc_tip' => true,
'wrapper_class' => 'form-row',
] );
}
// 2. Save the value when the product is updated
add_action( 'woocommerce_save_product_variation', 'save_variation_badge_field', 10, 2 );
function save_variation_badge_field( $variation_id, $loop ) {
$badge = isset( $_POST['variation_badge'][ $loop ] )
? sanitize_text_field( $_POST['variation_badge'][ $loop ] )
: '';
update_post_meta( $variation_id, '_variation_badge', $badge );
}
// 3. Pass the value to the front-end variation data (makes it available in JS)
add_filter( 'woocommerce_available_variation', 'add_badge_to_variation_data', 10, 3 );
function add_badge_to_variation_data( $data, $product, $variation ) {
$data['badge'] = get_post_meta( $variation->get_id(), '_variation_badge', true );
return $data;
}
// 4. Display it — update the DOM when the user selects a variation
add_action( 'woocommerce_single_product_summary', 'render_variation_badge_container', 25 );
function render_variation_badge_container() {
echo '<div class="variation-badge" style="display:none;"></div>';
}
// In your theme's JS file
jQuery( '.variations_form' ).on( 'found_variation', function( e, variation ) {
if ( variation.badge ) {
jQuery( '.variation-badge' ).text( variation.badge ).show();
} else {
jQuery( '.variation-badge' ).hide();
}
} );
jQuery( '.variations_form' ).on( 'reset_data', function() {
jQuery( '.variation-badge' ).hide();
} );
NOTE: The $loop parameter in variation hooks is the zero-based index of the variation in the form — it's how WooCommerce correlates the POST data back to the correct variation. Always use $loop as part of the field's name attribute, otherwise saving will only work for the first variation.