Checkout Fields (Billing & Shipping)
Checkout fields are the structured inputs WooCommerce uses to collect a customer’s billing and shipping address (plus contact details) at checkout. Each field is defined by a key (e.g., billing_first_name
, shipping_postcode
) and a config array describing its type, label, placeholder, required, priority/order, CSS classes, validation rules, and autocomplete hints.
WooCommerce builds these fields from two layers:
- Default address schema (via
WC_Countries::get_address_fields()
andwoocommerce_default_address_fields
) that localizes labels, formats, and “required” flags per country/locale (e.g.,state
vsprovince
, postcode formats, right-to-left nuances). - Checkout context (via
woocommerce_checkout_fields
,woocommerce_billing_fields
,woocommerce_shipping_fields
) that merges billing, shipping, and “additional” sections, applies your customizations, and renders inputs withwoocommerce_form_field()
.
At runtime, WooCommerce validates posted values (server-side), displays inline errors, and—on success—persists the data to order meta (and optionally user meta for logged-in customers). Themes and emails then consume those values for order views, admin screens, documents, and notifications.
Important: If you use the Checkout block (Cart & Checkout Blocks), classic hooks like
woocommerce_checkout_fields
don’t render UI. The block has its own extensibility API for adding/altering fields. Classic (shortcode) checkout customizations apply only when the classic template is active.
Built-in fields (typical)
Billing: first/last name, company, country, address_1, address_2, city, state, postcode, phone, email.
Shipping: first/last name, company, country, address_1, address_2, city, state, postcode.
Additional: order comments/notes.
Labels/required flags adapt to the selected country.
Common properties you’ll set
type
(text
, email
, tel
, select
, checkbox
), label
, placeholder
, required
, priority
, class
/label_class
/input_class
, validate
(e.g., postcode
, phone
), autocomplete
(e.g., given-name
, family-name
, address-line1
, postal-code
, tel
, email
), custom_attributes
(e.g., maxlength
, pattern
), options
(for selects).
Quick edits to built-in fields
add_filter('woocommerce_checkout_fields', function ($fields) {
// Make company optional, move phone up, tweak address_2 label
$fields['billing']['billing_company']['required'] = false;
$fields['billing']['billing_phone']['priority'] = 25;
$fields['shipping']['shipping_address_2']['label'] = __('Apartment, suite, etc.', 'your-txt');
// Stronger browser autofill hints
$fields['billing']['billing_first_name']['autocomplete'] = 'given-name';
$fields['billing']['billing_last_name']['autocomplete'] = 'family-name';
$fields['billing']['billing_postcode']['autocomplete'] = 'postal-code';
return $fields;
});
Country-wide tweaks (all address fields)
add_filter('woocommerce_default_address_fields', function ($fields) {
// Example: make state optional globally
$fields['state']['required'] = false;
return $fields;
});
Add a custom field (define → validate → save → display)
1) Define the field
add_filter('woocommerce_checkout_fields', function ($fields) {
$fields['billing']['billing_tax_id'] = [
'type' => 'text',
'label' => __('Tax ID', 'your-txt'),
'placeholder' => __('e.g., ES B12345678', 'your-txt'),
'required' => false,
'priority' => 120,
'class' => ['form-row-wide'],
'autocomplete'=> 'off',
];
return $fields;
});
2) Validate input
add_action('woocommerce_checkout_process', function () {
if (isset($_POST['billing_tax_id']) && $_POST['billing_tax_id'] !== '') {
$val = preg_replace('/\s+/', '', (string) $_POST['billing_tax_id']);
if (strlen($val) < 8) {
wc_add_notice(__('Please enter a valid Tax ID.'), 'error');
}
}
});
3) Save to the order (and optionally user)
add_action('woocommerce_checkout_create_order', function ($order, $data) {
if (isset($_POST['billing_tax_id'])) {
$order->update_meta_data('_billing_tax_id', sanitize_text_field($_POST['billing_tax_id']));
}
}, 10, 2);
// Persist for logged-in users so it pre-fills next time
add_action('woocommerce_checkout_update_user_meta', function ($customer_id, $data) {
if (isset($_POST['billing_tax_id'])) {
update_user_meta($customer_id, 'billing_tax_id', sanitize_text_field($_POST['billing_tax_id']));
}
}, 10, 2);
4) Show in admin + emails
add_action('woocommerce_admin_order_data_after_billing_address', function ($order) {
if ($v = $order->get_meta('_billing_tax_id')) {
echo '<p><strong>' . esc_html__('Tax ID', 'your-txt') . ':</strong> ' . esc_html($v) . '</p>';
}
});
// Order emails
add_filter('woocommerce_email_order_meta_fields', function ($fields, $sent_to_admin, $order) {
if ($v = $order->get_meta('_billing_tax_id')) {
$fields['billing_tax_id'] = ['label' => __('Tax ID', 'your-txt'), 'value' => $v];
}
return $fields;
}, 10, 3);
Blocks vs Classic checkout (important)
- Classic (shortcode) checkout: Use the PHP filters/actions shown above.
- Checkout block: Use the block’s extensibility APIs to register UI, store data via the Store API, and map it to order meta. Classic field filters won’t render inside the block.
UX & performance tips
- Use proper autocomplete tokens for better autofill and fewer typos.
- Keep the form minimal; only mark fields required if necessary (more friction = lower conversions).
- Consider address autocomplete (e.g., Places API) but always store discrete fields (line1, city, postcode).
- For B2B, make company + tax ID visible only when relevant (e.g., toggle by country or “business purchase” checkbox).
- Validate server-side (authoritative) and, optionally, client-side for instant feedback.
Privacy & compliance
- Don’t store payment card data; gateways handle that.
- Treat PII (names, phones, addresses) per GDPR/CCPA—define retention and export/erase support.
- If you send data to shipping/tax services, disclose it in your privacy policy.
Troubleshooting
- Field not showing: Wrong section key (
billing
/shipping
/additional
), low priority, or using classic filters with the block checkout. - Validation not firing: Ensure your hook runs (no fatal errors), and that
wc_add_notice()
is called before order creation. - Saved value missing: Use
woocommerce_checkout_create_order
(preferred) and a unique meta key; confirm it isn’t stripped by other hooks. - Locale oddities: Double-check
woocommerce_default_address_fields
changes against country formats (some countries have no states, variable postcode rules).