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:

  1. Default address schema (via WC_Countries::get_address_fields() and woocommerce_default_address_fields) that localizes labels, formats, and “required” flags per country/locale (e.g., state vs province, postcode formats, right-to-left nuances).
  2. 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 with woocommerce_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).