WooCommerce’s built-in checkout fields cover billing address, payment, and order notes, but many businesses need additional information at checkout such as a delivery time preference, gift message, VAT number, or purchase order reference. Adding a custom field involves three coordinated steps: rendering the field in the checkout form, validating and sanitizing its value on submission, and saving it to order meta so it appears in the order details and confirmation email. WooCommerce provides the woocommerce_after_order_notes action hook to add fields below the standard order notes textarea, keeping the field visually grouped with other optional inputs. The woocommerce_checkout_process hook runs before the order is created and is the correct place to call wc_add_notice() with an error if a required custom field is empty. Saving happens on the woocommerce_checkout_create_order hook in WooCommerce 3.x+, which passes the WC_Order object directly, making $order->update_meta_data() the modern alternative to update_post_meta(). Displaying the saved value in the order admin panel requires hooking into woocommerce_admin_order_data_after_billing_address. Injecting the field value into order confirmation emails uses woocommerce_email_order_meta to append it after the standard meta table. Using sanitize_text_field() and wp_unslash() on the POST value before saving prevents stored XSS via the custom field. The WooCommerce access control post covers another category of WooCommerce customisation that modifies the purchase flow. The nonce and sanitization guide explains the same input-handling principles applied here to checkout POST data. Testing the full flow — empty required field validation, valid submission, admin display, and email output — should be done in a staging environment before pushing to production to avoid checkout failures on live orders.
Problem: WooCommerce does not natively support extra checkout fields for custom business data such as VAT numbers or delivery preferences, requiring custom code that renders, validates, and persists the field through the order lifecycle.
Solution: Use the woocommerce_after_order_notes, woocommerce_checkout_process, and woocommerce_checkout_create_order hooks to render, validate, and save a custom checkout field, then display it in the admin panel and order email.
// 1. Render the custom field on checkout
add_action('woocommerce_after_order_notes', function($checkout) {
woocommerce_form_field('custom_vat_number', [
'type' => 'text',
'class' => ['form-row-wide'],
'label' => __('VAT number (optional)', 'textdomain'),
'placeholder' => 'GB123456789',
], $checkout->get_value('custom_vat_number'));
});
// 2. Validate the field before order creation
add_action('woocommerce_checkout_process', function() {
// Example: require VAT for B2B orders identified by a checkbox
if (!empty($_POST['is_b2b']) && empty($_POST['custom_vat_number'])) {
wc_add_notice(__('Please enter your VAT number for B2B orders.', 'textdomain'), 'error');
}
});
// 3. Save the field value to order meta
add_action('woocommerce_checkout_create_order', function($order, $data) {
if (!empty($_POST['custom_vat_number'])) {
$vat = sanitize_text_field(wp_unslash($_POST['custom_vat_number']));
$order->update_meta_data('_custom_vat_number', $vat);
}
}, 10, 2);
// 4. Show in admin order details
add_action('woocommerce_admin_order_data_after_billing_address', function($order) {
$vat = $order->get_meta('_custom_vat_number');
if ($vat) {
echo '<p><strong>' . esc_html__('VAT number', 'textdomain') . ':</strong> '
. esc_html($vat) . '</p>';
}
});
// 5. Include in order confirmation email
add_action('woocommerce_email_order_meta', function($order) {
$vat = $order->get_meta('_custom_vat_number');
if ($vat) {
echo '<p><strong>' . esc_html__('VAT number', 'textdomain') . ':</strong> '
. esc_html($vat) . '</p>';
}
});
NOTE: The woocommerce_checkout_create_order hook was introduced in WooCommerce 3.0 as a replacement for the deprecated woocommerce_checkout_update_order_meta — use the newer hook to ensure compatibility with HPOS (High-Performance Order Storage) introduced in WooCommerce 7.1.