Skip to content
DedicatedPHP Contact

Custom Fields in WooCommerce Checkout: From Purchase to Back Office

Learn how to add fields to the classic checkout or Checkout Block, validate input on the server, and verify that the data reaches the order and back office.

Custom WooCommerce checkout field saved to an order and viewed in the back office

A field added to checkout is not complete just because it appears on screen. To be useful, it must address a specific need, be validated on the server, be saved with the order, and reach the people or processes that need it. Each step can fail independently.

The implementation also depends on the active checkout flow. Classic checkout hooks do not add fields to the Checkout Block by themselves. Before writing code, identify which flow the store uses and design the complete path the data will take.

Decide whether the data belongs in checkout

Decide whether the data belongs in checkout — DedicatedPHP visual guide

Identify the task that requires the data and when it needs to be available. If it is essential for calculating the order, determining eligibility, or preparing delivery, it may need to be collected before the purchase is confirmed. If it only supports a later process, consider collecting it afterward to avoid adding unnecessary friction.

Document the purpose, who uses the field, whether it is required, and how long it should be retained. Check whether WooCommerce already has a standard field for that information: duplicating an address or phone number without defining which one takes precedence creates inconsistencies. For sensitive or high-impact data, limit collection and access, and review the applicable privacy requirements.

Identify the flow and add the field

Check the checkout page to see whether it uses the classic [woocommerce_checkout] shortcode or the Checkout Block. Also review the templates and extensions that modify that page. Recognizing the design is not enough: an extension can change the flow or its processing. Implement and test the appropriate mechanism, and do not assume that a field created for one flow will work in the other.

Classic checkout: field and order hooks

Classic checkout lets you modify fields with woocommerce_checkout_fields, validate them with woocommerce_after_checkout_validation, and save them during order creation with woocommerce_checkout_create_order. This example registers an optional field, rejects non-scalar input, and limits the text to 120 Unicode characters:

add_filter('woocommerce_checkout_fields', function ($fields) {
    $fields['order']['delivery_instructions'] = [
        'type'      => 'text',
        'label'     => 'Delivery instructions',
        'required'  => false,
        'priority'  => 25,
        'maxlength' => 120,
        'class'     => ['form-row-wide'],
    ];
    return $fields;
});

add_action('woocommerce_after_checkout_validation', function ($data, $errors) {
    $raw = $data['delivery_instructions'] ?? '';
    if (! is_scalar($raw)) {
        $errors->add('delivery_instructions', 'The value must be text.');
        return;
    }

    $value = trim(sanitize_text_field((string) $raw));
    if (preg_match_all('/./us', $value, $matches) === false) {
        $errors->add('delivery_instructions', 'The text has an invalid format.');
        return;
    }
    if (count($matches[0]) > 120) {
        $errors->add('delivery_instructions', 'The text cannot exceed 120 characters.');
    }
}, 10, 2);

add_action('woocommerce_checkout_create_order', function ($order, $data) {
    $raw = $data['delivery_instructions'] ?? '';
    if (! is_scalar($raw)) {
        return;
    }
    $value = trim(sanitize_text_field((string) $raw));
    if ($value !== '') {
        $order->update_meta_data('_delivery_instructions', $value);
    }
}, 10, 2);

The maxlength attribute provides a browser-side constraint; it does not replace server-side validation. The example counts Unicode code points using a UTF-8-compatible expression; some characters, such as certain emoji, occupy more than one UTF-16 code unit in the browser. If they need to be supported, explicitly define and test the length rule so the customer experience and server behavior do not disagree.

The example saves only a valid, non-empty value. If the field becomes required, also validate that it is present and return an error before creating the order. Use a stable internal name, and do not change it without planning a migration for existing data.

Checkout Block: Additional Checkout Fields API

For the Checkout Block, use WooCommerce's Additional Checkout Fields API instead of injecting HTML into the form. Register the field from a compatible plugin or theme, for example during woocommerce_init:

add_action('woocommerce_init', function () {
    if (! function_exists('woocommerce_register_additional_checkout_field')) {
        return;
    }

    woocommerce_register_additional_checkout_field([
        'id'       => 'my-plugin/delivery-instructions',
        'label'    => 'Delivery instructions',
        'location' => 'order',
        'type'     => 'text',
        'required' => false,
    ]);
});

The API integrates the field with the block flow and its associated order storage. If you need specific rules, check which sanitization and validation options the API supports in the actual installation, and test error handling end to end. Do not save the same value in parallel using another mechanism without deciding which is the source of truth.

Before deployment, check that the registration function exists and consult the documentation for the installed version. If it is unavailable, do not try to simulate compatibility with JavaScript: upgrade or choose an alternative compatible with the checkout in use, and test in a staging environment.

Validate, normalize, and persist on the server

Browser-side validation improves the user experience, but it is not a security control. On the server, check presence when required, type, length, format, and whether the value belongs to the allowed options. For closed lists, validate known identifiers, not arbitrary text sent by the client. If the data is optional, define whether missing and empty values mean the same thing. For an invalid value, show an understandable error and do not confirm the order while silently omitting data that is needed.

In the classic flow, $order->update_meta_data() uses the order CRUD API and avoids writing directly to tables or depending on a specific storage structure. In the Checkout Block, let the Additional Checkout Fields API manage the field association and retrieve the value through the order API or the documented mechanism for that integration; do not assume an internal metadata key.

To verify compatibility, check the WooCommerce settings to see whether High-Performance Order Storage (HPOS) or post-table-based storage is enabled. In a test environment with the same configuration, create one order for each flow, read it as a WC_Order object using wc_get_order($order_id), and check the data with the CRUD API—for example, $order->get_meta('_delivery_instructions') for the classic field. Update the order and read it again; do not use direct SQL queries against internal tables. Repeat these checks after changing storage or updating extensions that affect checkout.

View the data in the back office

The operational verification path is to create a test order, open WooCommerce > Orders, edit the order, and compare the displayed value with the one submitted. For classic checkout, if the metadata does not appear on the screen the team uses, you can add an admin display using the following order data hook:

add_action('woocommerce_admin_order_data_after_billing_address', function ($order) {
    $value = $order->get_meta('_delivery_instructions');
    if ($value !== '') {
        echo '<p><strong>Delivery instructions:</strong> '
            . esc_html($value) . '</p>';
    }
});

This hook and the example metadata apply to classic checkout; verify how they appear in the admin screen enabled for the store. With HPOS and the block-based order editor, check the actual result: do not assume that an extension for the classic admin panel automatically supports the new interface. Always use the order object and CRUD API to read or edit data.

Fields registered through the Checkout Block API can be integrated into order details. Verify this in WooCommerce > Orders using a test order. If other code needs to read the value, identify the location and retrieval mechanism for the installed version in the API documentation; do not guess the metadata key. If the value is not displayed, add an admin display compatible with that screen and test it with the configured storage and installed admin extensions.

If editing is required, define the authorized roles and save changes through the order CRUD API. Do not automatically display the data in emails, documents, exports, or public screens: add each destination only if there is an operational need, limit access, and always escape output.

Test the full lifecycle and prevent regressions

A test that only checks whether the field renders is insufficient. Verify the path from the form through to subsequent use, and cover at least the following:

  • Valid order with a value provided: validate it, save it to the correct order, and view it in the back office.
  • Optional field left empty and required field missing: verify the defined behavior for each case.
  • Tampered, non-scalar, overlong, or disallowed input: confirm that the server rejects it.
  • Text with accents and multibyte characters, including the exact limit; also check how it interacts with maxlength.
  • Order updates: check what changes, who can change it, and how the change propagates.
  • Classic checkout and Checkout Block: test the active flow and any planned change of flow.
  • Permissions and exposure: verify that only authorized users and processes receive the value.

Include regression tests for totals, validations, and order creation. If an automation consumes the field, also test what happens when the value is missing or its submission fails. A clear diagnostic signal is when the data appears in the browser but not in the order or the process that was supposed to use it.

Pre-deployment checklist

Pre-deployment checklist — DedicatedPHP visual guide
  • The purpose of the data and when it is collected are documented.
  • The mechanism matches the active checkout: hooks for the classic flow or the Additional Checkout Fields API for the Block.
  • The server validates type, format, and length; non-scalar input is rejected, and missing, empty, and invalid values each have defined handling.
  • The installation and order storage have been identified; reading and updating are tested with the CRUD API.
  • A test order confirms that the value can be read on the back-office screen in use, and access is restricted.
  • Tests cover Unicode characters, updates, errors, integrations, and checkout regressions.
Want to apply these ideas to your project?Let’s discuss your PHP platform.
View related service