GeneratorsWooCommerceCheckout Fields

Checkout Fields Generator

Extra checkout fields for whichever checkout renderer this site uses — the classic woocommerce_checkout_fields filter, or the Blocks Checkout's own field-registration API.

acme-checkout-fields.php
Saved just now
Fields
1 field
VAT numbervat_id
Required
Naming
Classic checkout keys are prefixed by location automatically — billing_vat_id, shipping_vat_id, and so on.
The woocommerce_checkout_fields filter — for the [woocommerce_checkout] shortcode.
<?php
// Generated with WP CodeKit — powered by GrowQuest (https://growquest.io).
/**
 * Plugin Name:       Checkout fields
 * Description:       Adds 1 field to classic checkout.
 * Version:           1.0.0
 * Requires PHP:      7.4
 * Requires Plugins:  woocommerce
 */

defined( 'ABSPATH' ) || exit;

/**
 * Add fields to the classic checkout form.
 *
 * @param array $fields Existing fields, grouped by section.
 * @return array
 */
function acme_checkout_fields( $fields ) {
	$fields['billing']['billing_vat_id'] = array(
		'label'       => __( 'VAT number', 'acme' ),
		'required'    => false,
		'class'       => array( 'form-row-wide' ),
		'placeholder' => __( 'e.g. GB123456789', 'acme' ),
	);

	return $fields;
}
add_filter( 'woocommerce_checkout_fields', 'acme_checkout_fields' );

/**
 * Save the values onto the order — before it is written, so this
 * runs whether the order ends up in wp_posts or the HPOS tables.
 *
 * @param WC_Order $order The order being created.
 * @param array    $data  The posted checkout data.
 */
function acme_save_checkout_fields( $order, $data ) {
	if ( ! empty( $_POST['billing_vat_id'] ) ) {
		$order->update_meta_data( '_billing_vat_id', sanitize_text_field( wp_unslash( $_POST['billing_vat_id'] ) ) );
	}
}
add_action( 'woocommerce_checkout_create_order', 'acme_save_checkout_fields', 10, 2 );

/**
 * Show the billing fields on the order edit screen.
 *
 * @param WC_Order $order The order.
 */
function acme_display_billing_meta( $order ) {
	$value = $order->get_meta( '_billing_vat_id' );

	if ( $value ) {
		echo '<p><strong>' . esc_html( __( 'VAT number', 'acme' ) ) . ':</strong> ' . esc_html( $value ) . '</p>';
	}
}
add_action( 'woocommerce_admin_order_data_after_billing_address', 'acme_display_billing_meta' );

About this generator

Checkout Fields Generator Online

A WooCommerce checkout field generator that writes both halves of the job: the field itself and the code that stores what the shopper typed. Pick classic checkout and you get a woocommerce_checkout_fields filter plus a save handler on woocommerce_checkout_create_order; pick Blocks checkout and you get woocommerce_register_additional_checkout_field() calls with correctly namespaced ids.

Switching output mode rewrites the whole file, because the two checkouts are genuinely different APIs rather than two flavours of the same one. Free to use, no account, and nothing you enter leaves the browser.

Reviewed Output tested on WordPress 6.8 · PHP 8.3

Why the checkout field generator beats a functions.php one-liner

Adding the field is the easy part. The snippets people find online stop at the woocommerce_checkout_fields filter and leave the value nowhere — not on the order, not in the admin, not in an email. And on a store running the Blocks Checkout, that filter does not render anything at all, because the block-based checkout never loads the classic form template. This generator writes the field, the save and the admin display together, in the flavour your store actually runs.

Two real output modes, not one with a caveat

Classic mode writes the woocommerce_checkout_fields filter with the section-keyed array WooCommerce expects. Blocks mode writes woocommerce_register_additional_checkout_field() calls on woocommerce_init instead — a different function, different locations, different storage.

Namespaced ids the Blocks API will accept

The Blocks field API rejects any id without a slash in it. The generator builds every id as prefix/field-name automatically, and shows you the exact $order->get_meta() call to read the value back.

A save handler that survives HPOS

Classic mode saves on woocommerce_checkout_create_order, before the order row is written, using $order->update_meta_data(). That works identically whether the order ends up in wp_posts or in the High-Performance Order Storage tables — unlike a woocommerce_checkout_update_order_meta handler calling update_post_meta().

The values actually appear on the order screen

Any billing field gets a display callback on woocommerce_admin_order_data_after_billing_address, and shipping fields get one on woocommerce_admin_order_data_after_shipping_address, so the data is visible to whoever packs the order.

Collisions and empty selects are caught

Two fields that resolve to the same location_key combination are an error, because the second silently overwrites the first. A select with no choices is an error too, with a one-click fix that adds two placeholder options.

An honest note about running both

The Checks tab always reminds you that classic and Blocks are separate rendering paths — a store where any shopper could still land on a shortcode checkout needs both outputs, not one instead of the other.

How does the Checkout Fields Generator work?

Four steps. Choose the checkout you are targeting, describe the fields, set the naming, then export.

  1. 01

    Pick the output mode

    Classic checkout or Blocks checkout. This changes which locations are available: billing, shipping and order notes for classic; address, contact info and additional order info for Blocks.

  2. 02

    Add the fields

    Give each field a key, a label and a type — text, select or checkbox. Set its location, mark it required if it is, add a placeholder, and for a select list the choices as value:Label pairs.

  3. 03

    Set the naming

    Choose the function prefix and text domain. These become the generated function names, the translation domain on every label, and in Blocks mode the namespace in front of every field id.

  4. 04

    Clear the checks, then export

    Resolve any duplicate keys, missing labels or empty selects flagged in the Checks tab, then copy the file or download it as a ready plugin.

Worked example — an optional VAT number on classic checkout

One optional billing text field, saved onto the order before it is written. Doc blocks and the admin display callback trimmed for length; everything else is exactly what classic mode emits.

function acme_checkout_fields( $fields ) {
	$fields['billing']['billing_vat_id'] = array(
		'label'       => __( 'VAT number', 'acme' ),
		'required'    => false,
		'class'       => array( 'form-row-wide' ),
		'placeholder' => __( 'e.g. GB123456789', 'acme' ),
	);

	return $fields;
}
add_filter( 'woocommerce_checkout_fields', 'acme_checkout_fields' );

function acme_save_checkout_fields( $order, $data ) {
	if ( ! empty( $_POST['billing_vat_id'] ) ) {
		$order->update_meta_data( '_billing_vat_id', sanitize_text_field( wp_unslash( $_POST['billing_vat_id'] ) ) );
	}
}
add_action( 'woocommerce_checkout_create_order', 'acme_save_checkout_fields', 10, 2 );

The same field in Blocks mode is a single woocommerce_register_additional_checkout_field() call on woocommerce_init with the id acme/vat-id — the slash is mandatory — and no save handler at all, because WooCommerce persists additional checkout fields itself.

WooCommerce checkout fields — frequently asked questions

The questions store builders ask most often when adding fields to checkout.

Why does my custom checkout field not show up on the Blocks Checkout?

Because the Blocks Checkout does not render the classic checkout templates, so the woocommerce_checkout_fields filter never runs. Fields for the block-based checkout must be registered with woocommerce_register_additional_checkout_field() on the woocommerce_init hook. The two systems are independent: a field added to one does not appear in the other.

Do I need both the classic and the Blocks version?

If any shopper can still reach a shortcode-based checkout page — an older checkout page, a payment-specific flow, a plugin that redirects to one — then yes, you need both, and both should write to a meta key you agree on up front. If the store has fully migrated to the Cart and Checkout blocks, the Blocks registration alone is enough.

Where is the value of a custom checkout field stored?

On the order, as order meta. The generated classic handler writes it with $order->update_meta_data() inside woocommerce_checkout_create_order, which runs before the order is saved, so one write covers both storage backends. Read it back later with $order->get_meta( '_billing_vat_id' ). Blocks fields are stored and read by WooCommerce itself under the namespaced id.

How do I make a custom checkout field required?

In classic mode set required to true in the field array and WooCommerce enforces it in its own checkout validation, showing the standard error notice. In Blocks mode set the required key on the registration call. Marking a field required does not retroactively affect orders already placed without it.

Why is my checkout field value missing from the order confirmation email?

Saving the value onto the order does not print it anywhere. The order edit screen needs a callback on woocommerce_admin_order_data_after_billing_address — which this generator writes for you — and emails need a separate hook such as woocommerce_email_order_meta_fields or woocommerce_email_order_meta. Nothing surfaces order meta automatically.

Can I add a field to the shipping section only?

Yes. In classic mode choose the shipping location and the field is added to $fields['shipping'], which WooCommerce only renders when the order is being shipped to a different address. In Blocks mode the equivalent location is address, which applies to both billing and shipping — the Blocks API has no shipping-only location.

Where do I paste the code from the Checkout Fields generator?

You have three good options and one bad one. Best is a small site-specific plugin — the generator can output one for you, and it keeps working when you change theme. A code-snippets plugin is fine too. A child theme's functions.php works but ties the code to that theme. Never edit the parent theme: the next update overwrites it and your code is gone.

Can I use the generated code in client work or a commercial plugin?

Yes. The output is ordinary WordPress API calls that you configured — there is no licence attached to it and no attribution required. Use it in client sites, commercial plugins and themes, or products you sell. The generated file carries a short credit comment you are free to delete.

The other WooCommerce generators in the library, plus the WordPress tools most often used alongside this one.