How to Add a Custom Required Field to WooCommerce Checkout (and Save to Order Meta & Emails)
Add, validate, save, display, and email a required WooCommerce checkout field without installing another checkout-field plugin.
WooCommerce’s checkout hooks make it possible to collect one additional piece of customer information without replacing the checkout template. This example adds a required Delivery instructions field, validates it, stores it as order meta, and displays it in the admin, customer account, and order emails.
Add the snippet to a child theme’s functions.php file or use a code snippets plugin. Test it on a staging site first, especially if the store uses a checkout-field customization plugin or the WooCommerce Checkout Block.
The snippet
// Add the field above the order notes on the classic checkout.add_action( 'woocommerce_after_order_notes', function( $checkout ) { woocommerce_form_field( 'delivery_instructions', array( 'type' => 'textarea', 'class' => array( 'form-row-wide' ), 'label' => __( 'Delivery instructions', 'my-site' ), 'placeholder' => __( 'For example: leave with reception.', 'my-site' ), 'required' => true, ), $checkout->get_value( 'delivery_instructions' ) );} );
// Add a server-side validation rule. Browser-side "required" is not enough.add_action( 'woocommerce_checkout_process', function() { $value = isset( $_POST['delivery_instructions'] ) ? sanitize_textarea_field( wp_unslash( $_POST['delivery_instructions'] ) ) : '';
if ( '' === $value ) { wc_add_notice( __( 'Please add your delivery instructions.', 'my-site' ), 'error' ); }} );
// Save the submitted value to the order.add_action( 'woocommerce_checkout_create_order', function( $order ) { if ( isset( $_POST['delivery_instructions'] ) ) { $value = sanitize_textarea_field( wp_unslash( $_POST['delivery_instructions'] ) ); $order->update_meta_data( '_delivery_instructions', $value ); }}, 10, 1 );
// Show the value in the order admin and customer order details.add_action( 'woocommerce_admin_order_data_after_shipping_address', 'my_site_show_delivery_instructions' );add_action( 'woocommerce_order_details_after_order_table', 'my_site_show_delivery_instructions' );
function my_site_show_delivery_instructions( $order ) { $value = $order->get_meta( '_delivery_instructions' );
if ( $value ) { echo '<p><strong>' . esc_html__( 'Delivery instructions', 'my-site' ) . ':</strong><br>' . nl2br( esc_html( $value ) ) . '</p>'; }}
// Add the value to all WooCommerce order emails.add_filter( 'woocommerce_email_order_meta_fields', function( $fields, $sent_to_admin, $order ) { $value = $order->get_meta( '_delivery_instructions' );
if ( $value ) { $fields['delivery_instructions'] = array( 'label' => __( 'Delivery instructions', 'my-site' ), 'value' => $value, ); }
return $fields;}, 10, 3 );How it works
woocommerce_after_order_notes renders the field on the classic checkout. The woocommerce_checkout_process callback performs validation on the server, so a request cannot bypass the requirement by disabling JavaScript. woocommerce_checkout_create_order saves the sanitized value through the CRUD order API, which works with both legacy post storage and high-performance order storage.
The leading underscore in _delivery_instructions keeps the meta key hidden from most generic custom-field interfaces. Use $order->get_meta( '_delivery_instructions' ) whenever the value is needed later.
Important limitation
This snippet targets the classic shortcode checkout. If the site uses the WooCommerce Checkout Block, use the block extensibility APIs or a block-compatible checkout-field extension instead; classic PHP checkout hooks do not automatically render fields inside the block.
For a block-based checkout, compare this approach with WooCommerce’s guide to additional checkout fields. For another checkout rule, see how to set a minimum order amount.