Skip to content

Powered by Grav CMS + Helios Theme

Checkout fields

You can edit every checkout field. This page explains how store administrators can customize the default form and how developers can add fields through an add-on.

New fields appear in the existing checkout form.

What a checkout field is

A checkout field is a control the customer fills in while placing an order, with rules about when it appears and what counts as an answer.

A custom field is different: it is a typed value stored on a record, such as a supplier code an operator types on an order afterwards. It is never collected from a shopper.

Checkout field Custom field
Collected from The customer, at checkout An operator or an add-on, on a record
Stored in The order's checkout-field data, or the address itself The custom field values table
Conditions Required, hidden and valid, per checkout None
Sorts and filters the admin list No Yes, when you say so

A VAT number typed at checkout is a checkout field. A supplier reference added later is a custom field. An answer collected at checkout does not become a filterable column of the order list.

The shipped fields

Every built-in checkout field has a record owned by core. Upgrading preserves the existing fields, their order and their markup.

Key Section Type Drawn
email contact email Always required
name contact text Always required
billing_company billing text Off by default
billing_address1, billing_address2 billing text
billing_country billing country
billing_city billing text
billing_region billing region
billing_postcode billing text
billing_phone billing tel Off by default
shipping_name … shipping_phone shipping The delivery address, when it differs from the billing one
notes order textarea

A core field can be reordered, relabelled, required, hidden or switched off. It cannot be deleted: the API answers a delete with a 409 and tells you to switch it off instead. The two company lines and the billing phone ship switched off.

Add a field

  1. In the admin, open KahunaCart → Checkout and select the Fields panel.
  2. Select New field. Give it a key, a label key, a type and a section.
  3. Set Required when, Hidden when and Valid when if the field needs any of them. Each is the same condition builder sales and payment methods use.
  4. Select Create field.

The field is on the checkout on the next page load.

To hide a built-in field, switch Enabled off. To reorder, change Position: the shipped fields are spaced by ten, so there is room between any two.

The three conditions

required, hidden and validation are written in the same condition language as sales, coupons and payment methods.

A document is a container of all, any or none, holding conditions and other containers. Each condition is an attribute, an operator and a value.

Empty and unset mean different things:

The document required hidden validation
Unset Never required Never hidden Anything is valid
An empty container Always required Always hidden Anything is valid
A document with conditions Required when it matches Hidden when it matches Valid when it matches

validation is written as the condition that holds when the answer is good. The refusal is its opposite.

The attributes you can test

Attribute Type Answers
cart_subtotal money What the lines charge, in minor units
cart_quantity number Every unit in the cart
cart_line_count number How many lines
cart_product product The products in the cart, for "the cart contains this product". A free gift does not count
cart_variant variant The variants in the cart. A free gift does not count
cart_has_physical bool Whether anything has to be delivered
billing_country text ISO 3166-1 alpha-2
billing_region text The subdivision code, where the country has one
shipping_country text The delivery country, falling back to the billing one
shipping_region text The same, for the subdivision
shipping_method text The chosen method's id
payment_provider text The chosen provider's slug
customer_group text The group code of the signed-in customer, or of the customer staff attached to the order. A guest gets the store's default group, and empty on a store with no groups. See Whose customer data a rule reads
customer_email_domain text Everything after the @ of the signed-in customer's email. Empty for a guest, whatever address they typed. See Whose customer data a rule reads
customer_logged_in bool Whether an account is signed in
field_<key> The field's own type What was typed into another field

The list is closed. An attribute not on it is refused by name when you save the field. GET /kahunacart/checkout-fields serves the list with every label translated, and the admin's condition builder is drawn from it.

A money condition is in a currency. On a store that sells in more than one currency cart_subtotal carries a currency beside its value, the store's own when it is missing, and it is false on an order in any other. A field required over 50.00 in dollars is not required on a euro order unless it also has a condition in euro, in an any container. The checkout field screen is read-only, so write it through the API. See Rules and conditions.

A namespaced key folds its slash to a double underscore, so acme/delivery_date is asked about as field_acme__delivery_date.

A condition on another field reads what was submitted, in one pass, so the answer never depends on field order and cannot loop. A field the customer cannot see reads as empty.

Important

A value posted for a field the checkout is not currently drawing is refused, not ignored, and so is a value under a key nobody declared. Both refusals happen before anything is written, and both apply to the storefront JSON API as well as to the form.

An empty box is not a posting, so a field the resolver hides after the customer changed their country on the same page does not turn a blank into a refused order.

Declare a field from an add-on

Subscribe to onKahunaCartRegisterCheckoutFields and register onto the registry.

PHP
public static function getSubscribedEvents(): array
{
    return ['onKahunaCartRegisterCheckoutFields' => ['onKahunaCartRegisterCheckoutFields', 0]];
}

public function onKahunaCartRegisterCheckoutFields(Event $event): void
{
    $event['registry']->register([
        'key' => 'vat_number',
        'owner' => 'kahunacart-eu-vat',
        'type' => 'text',
        'section' => 'billing',
        'position' => 65,
        'label_key' => 'PLUGIN_KAHUNACART_EU_VAT.NUMBER',
        'required' => [
            'container' => 'all',
            'children' => [
                ['attribute' => 'billing_country', 'operator' => 'in', 'value' => ['DE', 'FR', 'NL', 'IE']],
                ['attribute' => 'field_is_business', 'operator' => 'eq', 'value' => '1'],
            ],
        ],
    ], 'kahunacart-eu-vat');
}

The declaration is written into the field table on the first request that reads it. The field survives your add-on being switched off, and a merchant's edits survive it being switched back on.

Key Required Notes
key Yes [a-z][a-z0-9_]*, or vendor/name. Unique per store; the first registration wins and a duplicate is logged
label_key Yes A PLUGIN_*.* language key. Never English
owner Yes for an add-on Your plugin's name. core is reserved
type text, textarea, email, tel, select, checkbox, date, number, plus country and region
section contact, billing, shipping or order
position Order within the section
options For a select A list of {value, label_key}
required, hidden A condition document, or unset
validation A condition document plus a message_key
enabled Whether the form draws it. Defaults to true

A malformed declaration costs itself and nothing else. It is logged with a sentence explaining what is wrong, and the checkout still renders.

Where a value is stored

A core address field's answer goes into the order's billing or delivery address. Switching billing_company on puts a company on the invoice.

Everything else lands in the order's checkout-field data, one entry per field, each carrying the value and the label key that was on the field when the order was placed:

JSON
{
  "vat_number": {
    "value": "DE123456789",
    "label_key": "PLUGIN_ACME_VAT.NUMBER",
    "type": "text",
    "section": "billing",
    "position": 65
  }
}

The label travels with the value, so an invoice printed years later still says what the number beside it was, whatever happened to the field since.

In a theme

The checkout template renders every control through one partial, partials/kahunacart-checkout-field.html.twig. Override that file to restyle every checkout control at once. Keep the name and the value: a control posting a name the resolver did not offer is refused.

A theme that overrides the checkout template itself should draw the field model section by section through that partial too, or fields you add here never appear on its checkout. The Kahuna theme does this since 1.0.8, with a Kahuna-styled override of the partial: fields you add, the company line once it is switched on, and fields from add-ons such as the newsletter opt-in all appear, and a field is shown, required or hidden exactly as your rules say for the address the shopper has entered. Before 1.0.8 it drew its own fixed set of inputs.

The checkout template has four named regions, kahunacart_checkout_notices, kahunacart_checkout_express, kahunacart_checkout_fields and kahunacart_checkout_shipping. See Theming.

A section holding exactly one field takes that field's label as its legend and draws the control unlabelled, as the order-notes fieldset does. Autocomplete tokens are declared, not guessed, so a browser never fills a "delivery instructions" box with a street address.

API reference

Route Permission What it does
GET /kahunacart/checkout-fields kahunacart.products.manage Every field in draw order, with the condition vocabulary beside them
POST /kahunacart/checkout-fields kahunacart.products.manage Add a field
PUT /kahunacart/checkout-fields/{id} kahunacart.products.manage Change one. Only the keys you send are written
DELETE /kahunacart/checkout-fields/{id} kahunacart.products.manage Delete one. A core field answers 409

MCP tools: list_checkout_fields, create_checkout_field, update_checkout_field, delete_checkout_field.

Configuration

Key Default Effect
checkout.fields_enabled true Set to false and the checkout draws the shipped fields and ignores every declaration. An escape hatch, not a setting to run a store on