If you build WooCommerce sites for a living, you have done this before. A new client project starts, and the checkout needs the same three fields you set up on the last one. A VAT number. A delivery note. A “how did you hear about us?” dropdown.
So you set them up again. By hand. And then again on staging. Then someone on the client’s team edits one of them six months later, and the integration that reads it quietly stops working.
If you ship a plugin or a theme of your own, it is worse: you cannot set anything up by hand at all. Your users have to do it, follow instructions, and each one will do it slightly differently.
This post covers both ways to solve that on the block-based checkout: writing the registration code yourself, and shipping a definition file with your plugin or theme.
First, the important part: fields are registered in PHP, not React
The block-based checkout scared a lot of developers away at first, because the classic woocommerce_checkout_fields filter does nothing there. The good news is that you do not need to write any React to add a field. WooCommerce has a PHP API for it, woocommerce_register_additional_checkout_field(), introduced as experimental in WooCommerce 8.7 and stable since 8.9.
A minimal field looks like this:
add_action( 'woocommerce_init', function () {
woocommerce_register_additional_checkout_field(
array(
'id' => 'my-plugin/delivery-notes',
'label' => __( 'Delivery notes', 'my-plugin' ),
'location' => 'order',
'type' => 'text',
'required' => false,
)
);
} );Code language: PHP (php)
A few things that are easy to get wrong, and that the error messages only tell you about after the fact:
- The
idmust be namespaced. WooCommerce splits it on/and refuses anything without at least two parts.delivery-notesis rejected,my-plugin/delivery-notesis accepted. The namespace also ends up in the meta key, so pick it once and keep it. - Register on
woocommerce_init. Earlier than that and the class that handles registration may not exist yet. WooCommerce does defer the call for you if Blocks has not loaded, butwoocommerce_initis the hook to reach for. - There are three field types:
text,selectandcheckbox. That is the complete list. No date pickers, radio groups, file uploads, or number inputs. - There are three locations:
contact,addressandorder.addressrenders in both the billing and the shipping address forms, which is usually what you want, and occasionally not.
Validation, sanitizing, and the order confirmation
The registration array takes a few more keys worth knowing:
woocommerce_register_additional_checkout_field(
array(
'id' => 'my-plugin/vat-number',
'label' => __( 'VAT number', 'my-plugin' ),
'optionalLabel' => __( 'VAT number (if you need an invoice)', 'my-plugin' ),
'location' => 'address',
'type' => 'text',
'required' => false,
'show_in_order_confirmation' => true,
'attributes' => array(
'maxLength' => 20,
'pattern' => '[0-9]{9}',
'title' => __( 'Nine digits, no spaces', 'my-plugin' ),
'autocomplete' => 'off',
),
'sanitize_callback' => function ( $value, $field ) {
return trim( $value );
},
'validate_callback' => function ( $value, $field ) {
if ( '' !== $value && ! preg_match( '/^[0-9]{9}$/', $value ) ) {
return new WP_Error(
'my_plugin_invalid_vat',
__( 'Please enter a valid nine digit VAT number.', 'my-plugin' )
);
}
},
)
);Code language: PHP (php)
The attributes array is filtered against a whitelist. As of WooCommerce 11.0, you can pass maxLength, readOnly, pattern, autocomplete, autocapitalize and title, plus anything beginning with aria- or data-. Anything else is dropped, with a _doing_it_wrong() notice naming the keys that were rejected, including class. There is currently no way to add your own CSS class to a field you register. It is a known request, open at the time of writing. If you need to target your field with CSS, you have to use the class WooCommerce derives from the id itself, which is wc-block-components-address-form__ followed by your id with the slash turned into a hyphen. For the field above, that is .wc-block-components-address-form__my-plugin-vat-number. It works, but it is not a documented contract, so keep an eye on it.
There is a better option, though. A data- attribute is allowed, and [data-my-plugin-field="vat"] is a perfectly good CSS selector, as well as the cleanest way for your own JavaScript to find the field:
'attributes' => array(
'data-my-plugin-field' => 'vat',
),Code language: PHP (php)
Prefer that over the derived class: it is yours, you chose it, and it will not change under you.
Reading the values back
Values are stored in order and customer meta, namespaced the same way as the id:
$vat = $order->get_meta( '_wc_billing/my-plugin/vat-number' );Code language: PHP (php)
The prefix depends on the location: _wc_other/ for contact and order fields, _wc_billing/ and _wc_shipping/ for address fields, which means an address field is stored twice, once per address.
What this adds up to
For one field, the code above is perfectly reasonable, and if that is all you need, stop here. You do not need a plugin for it.
It stops being reasonable somewhere around the point where you want:
- half a dozen fields across all three locations
- fields that are only shown, or only required, in some situations
- to remove or rename the core address fields as well as add your own
- the client to be able to see what is configured without reading your code
- and all of it to be the same on the fifteen sites you maintain
At that point, you are not adding a checkout field anymore; you are maintaining a small configuration system, and every project you start is another copy to keep in sync.
The other way: set them up once, ship the file
Simple Checkout Fields Manager for WooCommerce is a no-code plugin: you add and manage fields from a settings screen, including removing and renaming the WooCommerce core address fields, and setting conditional rules for when a field is shown or required.
Since version 8.1, it also does what matters if you’re building the site rather than running it. You can bundle a set of field definitions with your own plugin or theme:
- Set up the fields once on a development or staging site using the plugin’s settings screen.
- Click Export field definitions and save the file into your plugin or theme.
- Register its path with one filter:
add_filter( 'swcbcf_bundled_field_definition_files', function ( $files ) {
$files[] = __DIR__ . '/checkout-fields.json';
return $files;
} );Code language: PHP (php)
That is the whole integration.
What you get from it:
- The fields live in your repository. Commit the file, diff it between releases, roll it back like any other part of your code. Nothing is written to the site’s database.
- They cannot be edited on the site. They show up in the settings screen with a lock icon, no Edit and no Delete, so a client cannot rename the field your integration reads.
- Every site is identical. Same fields, same order, same conditional rules, from the same file.
- Core address fields come along too. You can bundle removals, renames, and priority changes for the core address fields in the same file.
When you need to change something, set it up on your development site again and export a new file. Do not edit the JSON by hand: its structure can change between plugin versions, and hand-written files are not supported. Exporting is exactly what makes the file safe to commit and diff.
Full instructions, including where to register the filter and what happens if two files define the same field, are in the documentation.
Also new in 8.1
- Conditional rule for first-time customers. Show a field, or make it required, only for customers with no previous orders. Checked by account for logged-in customers and by email address for guests.
- A “Full width” option for address fields. WooCommerce lays the address form out two fields per line, so a field can end up alone on its line and stretched. You can now tell a field to take a whole line, and the ones after it pair up again from a clean start.
The full list is on the changelog.
Wrapping up
If you need one checkout field, register it in PHP and move on. If you need a checkout configuration that you ship, version, and keep identical across every site you build, that is a different job, and Simple Checkout Fields Manager for WooCommerce will do it without you maintaining any of it.
