WooCommerce: Add Custom Fields to Product Variations

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.