Contents
Overview
This article is a comprehensive, practical tutorial that explains how to customize WooCommerce checkout fields using hooks. It covers the most common tasks: adding new fields, removing or modifying existing fields, changing order and priorities, validating input, saving values to orders and user meta, showing fields in admin and emails, and conditional/JavaScript-driven behaviors. Every example is complete and copy-paste ready for a themes functions.php or a custom plugin file. Use a child theme or custom plugin for production.
Core concepts and hooks
WooCommerce checkout fields live in an array returned by the filter woocommerce_checkout_fields. You can add, edit or remove fields by hooking into that filter. Additional hooks and actions youll commonly use:
- woocommerce_checkout_process — server-side validation before order is created.
- woocommerce_checkout_update_order_meta — save posted checkout fields onto the order meta.
- woocommerce_admin_order_data_after_billing_address — display custom fields in the admin order view.
- woocommerce_email_order_meta_fields — include custom fields in order emails.
- woocommerce_thankyou / woocommerce_view_order — display custom fields on the thank-you and order view pages.
- wc_add_notice() — add checkout errors or notices from PHP validation hooks.
Structure of a checkout field array
Each field is an associative array. The most commonly used keys are:
Key | Meaning |
type | Input type: text, textarea, select, checkbox, radio, password, date, etc. |
label | Field label shown to the user. |
placeholder | Placeholder text for input types that support it. |
required | Boolean true/false whether field is mandatory. |
class | Array of classes added to the field wrapper for layout. |
label_class | Array of classes applied to the label element. |
options | For select and radio: associative array of value => label. |
priority | Number controlling order among fields. |
default | Default value for the field. |
Where checkout fields live
Fields are grouped by sections in the checkout fields array:
- billing — billing_ fields.
- shipping — shipping_ fields.
- account — account creation fields.
- order — order comments, custom order fields.
Basic examples
1) Remove an existing field
Remove billing company field:
add_filter( woocommerce_checkout_fields, prefix_remove_billing_company ) function prefix_remove_billing_company( fields ) { if ( isset( fields[billing][billing_company] ) ) { unset( fields[billing][billing_company] ) } return fields }
2) Modify an existing field (placeholder, label, required)
Change the billing phone placeholder and mark as required:
add_filter( woocommerce_checkout_fields, prefix_modify_billing_phone ) function prefix_modify_billing_phone( fields ) { fields[billing][billing_phone][placeholder] = Enter a contact phone number fields[billing][billing_phone][label] = Contact Phone fields[billing][billing_phone][required] = true return fields }
3) Add a new text field
Add a new required text field to the billing section and save it to the order:
add_filter( woocommerce_checkout_fields, prefix_add_billing_delivery_instructions ) function prefix_add_billing_delivery_instructions( fields ) { fields[billing][billing_delivery_instructions] = array( type => text, label => Delivery instructions, placeholder => Leave any delivery notes here, required => false, class => array( form-row-wide ), clear => true, priority => 120, ) return fields } // Save to order meta add_action( woocommerce_checkout_update_order_meta, prefix_save_delivery_instructions ) function prefix_save_delivery_instructions( order_id ) { if ( ! empty( _POST[billing_delivery_instructions] ) ) { update_post_meta( order_id, _billing_delivery_instructions, sanitize_text_field( wp_unslash( _POST[billing_delivery_instructions] ) ) ) } }
4) Add a select (dropdown) field
Example: preferred delivery time slot:
add_filter( woocommerce_checkout_fields, prefix_add_delivery_timeslot ) function prefix_add_delivery_timeslot( fields ) { fields[order][delivery_timeslot] = array( type => select, label => Preferred delivery time, required => true, class => array( form-row-wide ), options => array( => Select a timeslot, morning => Morning (8am - 12pm), afternoon => Afternoon (12pm - 4pm), evening => Evening (4pm - 8pm), ), priority => 200, ) return fields } add_action( woocommerce_checkout_update_order_meta, prefix_save_delivery_timeslot ) function prefix_save_delivery_timeslot( order_id ) { if ( isset( _POST[delivery_timeslot] ) ) { update_post_meta( order_id, _delivery_timeslot, sanitize_text_field( wp_unslash( _POST[delivery_timeslot] ) ) ) } }
Validation: enforcing rules server-side
Always validate server-side in addition to client-side JS. Use the action woocommerce_checkout_process to add validation errors using wc_add_notice.
add_action( woocommerce_checkout_process, prefix_validate_delivery_timeslot ) function prefix_validate_delivery_timeslot() { if ( isset( _POST[delivery_timeslot] ) empty( _POST[delivery_timeslot] ) ) { wc_add_notice( Please choose a preferred delivery time., error ) } // Example regex validation for custom text field if ( isset( _POST[billing_delivery_instructions] ) ) { value = trim( wp_unslash( _POST[billing_delivery_instructions] ) ) if ( value !== ! preg_match( /^[A-Za-z0-9 ,.-]{2,200}/, value ) ) { wc_add_notice( Delivery instructions contain invalid characters., error ) } } }
Display custom fields in the admin order view
To display saved meta in the backend order page:
add_action( woocommerce_admin_order_data_after_billing_address, prefix_display_delivery_meta_in_admin, 10, 1 ) function prefix_display_delivery_meta_in_admin( order ) { timeslot = get_post_meta( order->get_id(), _delivery_timeslot, true ) instructions = get_post_meta( order->get_id(), _billing_delivery_instructions, true ) if ( timeslot ) { echo ltpgtltstronggtPreferred delivery:lt/stronggt . esc_html( timeslot ) . lt/pgt } if ( instructions ) { echo ltpgtltstronggtDelivery notes:lt/stronggt . esc_html( instructions ) . lt/pgt } }
Include custom fields in order emails
Simple approach: use the filter woocommerce_email_order_meta_fields to add the fields to order emails:
add_filter( woocommerce_email_order_meta_fields, prefix_add_delivery_meta_to_emails, 10, 3 ) function prefix_add_delivery_meta_to_emails( fields, sent_to_admin, order ) { fields[delivery_timeslot] = array( label => Delivery timeslot, value => get_post_meta( order->get_id(), _delivery_timeslot, true ), ) fields[delivery_instructions] = array( label => Delivery instructions, value => get_post_meta( order->get_id(), _billing_delivery_instructions, true ), ) return fields }
Show custom fields on thank-you and order view pages
add_action( woocommerce_thankyou, prefix_show_delivery_info_on_thankyou, 20 ) add_action( woocommerce_view_order, prefix_show_delivery_info_on_order_view, 20 ) function prefix_show_delivery_info_on_thankyou( order_id ) { prefix_output_delivery_info( order_id ) } function prefix_show_delivery_info_on_order_view( order_id ) { prefix_output_delivery_info( order_id ) } function prefix_output_delivery_info( order_id ) { timeslot = get_post_meta( order_id, _delivery_timeslot, true ) instructions = get_post_meta( order_id, _billing_delivery_instructions, true ) if ( timeslot ) { echo ltpgtltstronggtPreferred delivery:lt/stronggt . esc_html( timeslot ) . lt/pgt } if ( instructions ) { echo ltpgtltstronggtDelivery notes:lt/stronggt . esc_html( instructions ) . lt/pgt } }
Populate default values (e.g., from user meta)
If the user is logged in, you can set field defaults using the filter. This helps pre-fill values from previous orders or user meta.
add_filter( woocommerce_checkout_fields, prefix_prefill_from_user_meta ) function prefix_prefill_from_user_meta( fields ) { if ( is_user_logged_in() ) { user_id = get_current_user_id() prev_timeslot = get_user_meta( user_id, preferred_delivery_timeslot, true ) if ( prev_timeslot ) { fields[order][delivery_timeslot][default] = prev_timeslot } } return fields }
Re-ordering fields and priority
You can move fields by changing the priority (or by reconstructing the array order). Lower numbers appear first. Example: move order comments to appear earlier:
add_filter( woocommerce_checkout_fields, prefix_reorder_order_comments ) function prefix_reorder_order_comments( fields ) { if ( isset( fields[order][order_comments] ) ) { fields[order][order_comments][priority] = 50 } return fields }
Conditional fields and client-side behavior
When you need fields to appear/disappear depending on another field (for example, show delivery instructions only if deliver later is checked), you will add a field and then use JS to toggle visibility. Always still validate on the server.
Example: add a checkbox that toggles the visibility of an instructions text field, plus simple JS to show/hide it.
add_filter( woocommerce_checkout_fields, prefix_add_delivery_toggle_fields ) function prefix_add_delivery_toggle_fields( fields ) { fields[order][deliver_later] = array( type => checkbox, label => Deliver later, required => false, class => array( form-row-wide ), priority => 110, ) fields[order][delivery_instructions_conditional] = array( type => text, label => Delivery instructions (only if Deliver later is checked), required => false, class => array( form-row-wide, conditional-delivery-instructions ), priority => 115, ) return fields }
Now include a small JS snippet to toggle the visibility. Insert this JS into your theme footer or enqueue a small script file. The example below demonstrates inline script in a plugin you should enqueue it properly.
jQuery( function( ) { function toggleDeliveryInstructions() { var checked = ( #deliver_later ).is( :checked ) if ( checked ) { ( .conditional-delivery-instructions ).closest( .form-row ).show() } else { ( .conditional-delivery-instructions ).closest( .form-row ).hide() } } // Initial toggle on page load toggleDeliveryInstructions() // Toggle on change ( document ).on( change, #deliver_later, toggleDeliveryInstructions ) })
Important: replace #deliver_later selector with the actual field ID rendered in your checkout. WooCommerce typically outputs field IDs like deliver_later (it uses the array key), but confirm in the page HTML.
Field types: quick reference and examples
- text — simple single-line input
- textarea — multi-line text
- select — dropdown with options
- radio — multiple choice radio buttons
- checkbox — tickbox (boolean)
- password — password input (rare on checkout)
- date — if you use a date picker, ensure the date input is supported by your theme or add custom JS
Example: textarea field
add_filter( woocommerce_checkout_fields, prefix_add_order_notes_textarea ) function prefix_add_order_notes_textarea( fields ) { fields[order][customer_notes] = array( type => textarea, label => Extra notes, placeholder => Anything else we should know?, required => false, class => array( form-row-wide ), priority => 210, ) return fields }
Tips for compatibility and best practices
- Use sanitization functions when saving data: sanitize_text_field(), esc_textarea(), sanitize_email() etc.
- Escape output with esc_html(), esc_attr() or esc_textarea() when printing values.
- Do not rely only on JS for validation — always validate server-side using woocommerce_checkout_process.
- When adding fields that affect price or shipping, recalculate on the server and adjust order totals appropriately.
- Test with guest checkout and logged-in users test with address fields and different payment gateways.
- Keep the field keys unique and use a consistent prefix for meta keys (e.g., _myplugin_fieldname) to avoid collisions.
- Prefer hooks and filters rather than editing WooCommerce templates its future-proof and upgrade-safe.
Advanced topics
Conditional server-side modifications
You may want to modify the checkout fields in PHP based on cart contents, shipping method, or payment gateway. Because the checkout is built early, you can check conditions inside the filter callback. Example: add a field only when a specific product ID is in the cart:
add_filter( woocommerce_checkout_fields, prefix_add_if_product_in_cart ) function prefix_add_if_product_in_cart( fields ) { target_product_id = 123 // set your product ID found = false foreach ( WC()->cart->get_cart() as cart_item ) { if ( cart_item[product_id] == target_product_id ) { found = true break } } if ( found ) { fields[order][special_instructions_for_product] = array( type => text, label => Instructions for product 123, required => false, priority => 205, ) } return fields }
Custom field types and rendering
If you need a field type that WooCommerce does not support out-of-the-box, you can use the woocommerce_form_field function to render a custom field and use hooks such as woocommerce_after_checkout_billing_form to output custom HTML. However, when possible, reuse the built-in types for better compatibility with themes and styling.
Common pitfalls
- Expecting field keys to always have certain IDs — always inspect the generated HTML to target selectors correctly for JS.
- Using unchecked superglobals: always wrap _POST access with isset() and use wp_unslash() before sanitization.
- Forcing required fields with JS only — users with JS disabled will skip validation unless you validate server-side.
- Storing sensitive data without encryption — do not store unneeded sensitive data in post meta.
Checklist before deploying to production
- Test with multiple payment gateways and shipping methods.
- Test with responsive mobile layouts and different themes.
- Confirm data is saved to order meta and displayed in admin and emails.
- Ensure translations: wrap user-facing strings with __() or _e() and load your text domain.
- Backup your site and test on staging before real deployment.
Quick reference code snippets
Remove field:
add_filter( woocommerce_checkout_fields, function( fields ) { unset( fields[billing][billing_company] ) return fields } )
Save posted checkout value to order meta (sanitized):
add_action( woocommerce_checkout_update_order_meta, function( order_id ) { if ( ! empty( _POST[my_custom_field] ) ) { update_post_meta( order_id, _my_custom_field, sanitize_text_field( wp_unslash( _POST[my_custom_field] ) ) ) } } )
Display saved meta in admin order:
add_action( woocommerce_admin_order_data_after_billing_address, function( order ) { val = get_post_meta( order->get_id(), _my_custom_field, true ) if ( val ) { echo ltpgtltstronggtMy custom field:lt/stronggt . esc_html( val ) . lt/pgt } } )
Summary
Customizing WooCommerce checkout fields using hooks is a robust and upgrade-safe approach. Use the woocommerce_checkout_fields filter to add/modify/remove fields, validate with woocommerce_checkout_process, save with woocommerce_checkout_update_order_meta, and display values where needed (admin, emails, thank you page). Always sanitize and escape, validate server-side, and test thoroughly across different scenarios and themes.
Useful links
|
Acepto donaciones de BAT's mediante el navegador Brave 🙂 |