Editing the WooCommerce Checkout Fields
The filter still works, but it only governs the classic checkout shortcode. On a store using the checkout block it runs and changes nothing, which is why so many working snippets suddenly stopped applying.
Before you touch the filter, confirm which checkout the store actually renders. woocommerce_checkout_fields governs the classic shortcode checkout. A store using the checkout block builds its form from a separate registry, so the filter fires, returns a modified array, and nothing on the page changes. That single distinction accounts for most snippets that "stopped working" without anyone editing them.
The shape of the array
The filter receives one nested array with four top-level groups:
| Group | Contains | Also used by |
|---|---|---|
billing |
Billing address and contact fields | Order emails, tax location |
shipping |
Shipping address fields | Shipping zone matching |
account |
Username and password when registering at checkout | Account creation |
order |
Order notes | The order screen |
Each field is itself an array of properties — label, required, placeholder, class, priority, type. Editing one means reaching two levels down:
add_filter( 'woocommerce_checkout_fields', 'ti_checkout_fields' );
function ti_checkout_fields( $fields ) {
$fields['billing']['billing_phone']['required'] = false;
$fields['billing']['billing_company']['label'] = __( 'Company (optional)', 'your-textdomain' );
$fields['order']['order_comments']['placeholder'] = __( 'Delivery notes', 'your-textdomain' );
unset( $fields['billing']['billing_address_2'] );
return $fields;
}
Returning $fields is not optional. A callback that modifies the array and forgets the return hands WooCommerce null, and the checkout renders with no fields at all — a dramatic failure that looks like a fatal error but is not one.
Priority controls order, and it is not the hook priority
Every field carries its own priority property, and that is what determines display order within a group. It has nothing to do with the priority argument of add_filter:
$fields['billing']['billing_phone']['priority'] = 25;
Billing fields are numbered in tens, so the gaps are deliberate — an odd number like 25 slots cleanly between two defaults without renumbering anything. Reordering by rewriting the whole array instead is what produces checkouts where the postcode sits above the street.
Fields that are not safe to remove
Unsetting a field removes the input, not the dependency on the value behind it:
billing_countrydecides the tax rate and matches the shipping zone. Remove it and both fall back to store defaults, which is wrong the moment a second country is served. If the store genuinely ships to one country, set a default server-side rather than deleting the field.billing_postcodefeeds postcode-based shipping zones. A store using them will silently stop matching, in the way described under WooCommerce shipping setup.billing_emailis where every transactional message goes. Without it the order completes and the customer hears nothing, which surfaces later as emails not sending.billing_first_nameandbilling_last_nameappear on invoices and in the order list. Removing them leaves orders that are hard to identify in support.
Making a field optional is nearly always the better move than removing it, because the column still exists and the value is still stored when present.
The locale layer sits on top
After your filter runs, WooCommerce applies country-specific overrides. A field you marked optional can be forced back to required when the customer selects a country whose locale demands it, and a label you set can be replaced with a localised one.
This is why a change appears to work until someone selects a different country. The locale rules live behind woocommerce_get_country_locale, and a field that must stay optional everywhere has to be adjusted there as well as in the field array. Testing with a single country hides the entire problem.
Verify before shipping the change
Run through four checks, in this order:
- Confirm the checkout page renders the classic shortcode and not the block. If it is the block, stop — the filter is the wrong tool and no amount of editing will help.
- Load the checkout with an empty cart and a full one; some fields only render when shipping is required.
- Switch the country twice and confirm your required and optional states survive the locale pass.
- Place one real test order and open the resulting order in the admin, confirming the values you kept are stored against it.
If the form itself fails to submit after the edit, the cause is usually a removed field that validation still expects rather than the markup, and the route through that is checkout not working.
Frequently asked
- The store is almost certainly rendering the checkout block rather than the classic shortcode. The block builds its form from its own field registry, so the filter runs and its return value is never read. Check which of the two the checkout page contains before debugging the snippet.
- Because those are different field sets. The address forms under My Account come from woocommerce_default_address_fields, while checkout-only changes come from woocommerce_checkout_fields. Filter the former if you want both to match.
- No. Tax rates and shipping zones are resolved from the customer country, so removing the field leaves both calculating against a fallback. Hide it with a default value server-side if the store ships to exactly one country.