Encodera

Thanks for reading!

Donate to the site — we work hard for you.

Contents

Checkout fields and validation

The checkout form consists of fields: name, address, phone, a note. Unneeded fields are removed, required flags are changed, your own are added. In the classic checkout (the [woocommerce_checkout] shortcode) one filter does all of this; a replacement for the block one is at the end of the lesson.

The code of this lesson goes into includes/checkout.php.

Removing and changing a field

// wp-content/plugins/acme-shop/includes/checkout.php
add_filter('woocommerce_checkout_fields', function (array $fields): array {
    unset($fields['billing']['billing_company']);
 
    $fields['billing']['billing_phone']['required'] = true;
 
    return $fields;
});

The filter receives all form fields grouped by section and returns the changed array. Open /checkout/: the Company name field is gone and the phone has the asterisk of a required field.

Form sections

SectionWhat is in itExample key
billingBilling detailsbilling_first_name, billing_email
shippingShipping addressshipping_city, shipping_postcode
orderAdditionalorder_comments

Your own field

// wp-content/plugins/acme-shop/includes/checkout.php
add_filter('woocommerce_checkout_fields', function (array $fields): array {
    $fields['order']['acme_delivery_note'] = [
        'type' => 'textarea',
        'label' => 'Note for the courier',
        'placeholder' => 'Door code, floor',
        'required' => false,
        'priority' => 20,
    ];
 
    return $fields;
});

One more field appears under Order notes. The type parameter accepts text, textarea, select, checkbox, email, tel; priority sets the order within the section.

Validation

// wp-content/plugins/acme-shop/includes/checkout.php
add_action('woocommerce_after_checkout_validation', function (array $data, WP_Error $errors) {
    $digits = preg_replace('/\D/', '', $data['billing_phone'] ?? '');
 
    if (strlen($digits) < 10) {
        $errors->add('acme_phone', 'Enter the full phone number with the area code.');
    }
}, 10, 2);

The hook fires after the standard checks, when the customer has pressed Place order. $data holds the field values, and your errors are added to $errors. Enter the phone 123: the order is not placed and your message appears above the form.

Saving into the order

The value of your own field has to be written into the order by you:

// wp-content/plugins/acme-shop/includes/checkout.php
add_action('woocommerce_checkout_create_order', function (WC_Order $order, array $data) {
    if (!empty($data['acme_delivery_note'])) {
        $order->update_meta_data('_acme_delivery_note', sanitize_textarea_field($data['acme_delivery_note']));
    }
}, 10, 2);

The hook is called before the order is saved, so save() is not needed.

Showing it in the admin panel

// wp-content/plugins/acme-shop/includes/checkout.php
add_action('woocommerce_admin_order_data_after_shipping_address', function (WC_Order $order) {
    $note = $order->get_meta('_acme_delivery_note');
 
    if ($note !== '') {
        echo '<p><strong>For the courier:</strong> ' . esc_html($note) . '</p>';
    }
});

Place an order with a note and open it in WooCommerce → Orders: the text is visible under the shipping address.

The block checkout

In the block variant the woocommerce_checkout_fields filter and the validation hook are not called. A field is registered with a separate function:

// wp-content/plugins/acme-shop/includes/checkout.php
add_action('woocommerce_init', function () {
    woocommerce_register_additional_checkout_field([
        'id' => 'acme/delivery-note',
        'label' => 'Note for the courier',
        'location' => 'order',
        'type' => 'text',
        'required' => false,
    ]);
});

The ID consists of the plugin prefix and a name separated by a slash. location is the form section: contact, address or order. WooCommerce itself saves the value and shows it in the order and in emails. The text, select and checkbox types are supported.

Common mistake

The field was added with the filter, but the woocommerce_checkout_create_order hook was not. The customer fills the field, the order is created, but the value is saved nowhere. In the classic checkout only the standard address fields are saved automatically; your own need a separate hook.

Thanks for reading!

Donate to the site — we work hard for you.