Object templates
Object templates are short Twig templates for customising generated text: SKUs for variants with no SKU, cart line names, payment descriptions, order references, invoice file names and order email subjects. You edit them all on one admin screen.
Before you begin
- Reading, saving and previewing a template all need
kahunacart.settings.
The slots
A slot is one place a template is rendered. Each has a default and a fixed list of variables it may read.
| Slot | Shipped default | May read | Rendered when |
|---|---|---|---|
sku_format |
(empty, no SKU is generated) | product.id, product.slug, product.title, variant.id, variant.title, seq |
A variant is saved through the admin API with a blank SKU |
product_title_format |
{{ product.title }} |
product.title, product.slug, variant.sku |
A line for a product with no variant label is added to a cart |
variant_title_format |
{{ product.title }} — {{ variant.label }} |
product.title, product.slug, variant.title, variant.label, variant.sku |
A line for a variant is added to a cart |
order_description |
{{ store.name }} order {{ order.reference }} |
order.number, order.reference, order.item_count, order.total, order.currency, store.name |
A payment starts. The provider reads it as $context->metadata['description'] |
order_reference |
#{{ order.number }} |
order.number, order.id, order.date, order.currency, store.name |
An order completes. The result is frozen on the order |
pdf_filename |
invoice-{{ invoice.number\|default(order.number) }} |
invoice.number, order.number, order.reference, order.date, store.name |
An invoice PDF is downloaded |
email_subject.<code> |
{{ default }} |
default, order.number, order.reference, order.total, order.currency, store.name, customer.name |
An order email is rendered. One slot per notification with a kind, such as email_subject.order.shipped |
store.name is invoice.store_name, or the site title when that is empty. order.date is the completion date as YYYY-MM-DD in UTC. order.total is formatted money in the order's currency. default on a subject slot is the translated subject the email ships with, already in the order's language. variant.label is the variant's own title, or its option combination for a generated variant. seq is the next value of the sku sequence.
Edit a template
- In the admin sidebar, click KahunaCart, then Object templates.
- Edit the template under the slot you want.
- Click Preview. For an order-facing slot, type an order id into Preview order-facing slots against order # first to render against a real order instead of the sample.
- Click Save.
The template applies to everything rendered from now on. Restore shipped format puts the default back. An empty template does the same.
The endpoints
| Method | Path | Answers |
|---|---|---|
GET |
/kahunacart/object-templates |
Every slot with its default, its template, the variables it may read and the preview sample. ?slot= reads one |
PUT |
/kahunacart/object-templates |
Saves one, from {"slot": "...", "template": "..."} |
POST |
/kahunacart/object-templates/preview |
Renders one without saving, as an HTML fragment |
All three need kahunacart.settings. The MCP tools are get_object_template and set_object_template; the preview has no tool. See REST API.
Each slot's saved value lives in the object-templates namespace of the settings store. See Custom fields and the settings store.
The sandbox
Every template renders in a Twig sandbox with a fixed allowlist per slot, enforced at preview, at save and at render.
A template may read only the variables its slot lists, named exactly. Allowing order.number does not allow {{ order }} or anything beneath the path. Anything else is refused with a message naming the variable:
invoice-{{ order.customer.email }}
saved into pdf_filename answers The pdf_filename template reads "order.customer.email", which it is not allowed to., in the preview box and as a 422 from the API.
The sandbox allows the tags if and set; the filters upper, lower, capitalize, title, trim, replace, slice, format, date, default, number_format, abs, round, length, join, split, first, last, escape, e, striptags and pad; and the functions date, min and max.
No for, no include, no methods and no properties on objects: every variable is a plain value or a plain map. Anything outside that list is refused with the tag, filter or function named.
pad is KahunaCart's own: {{ order.number|pad(6) }} prints 001042. Use pad for zero-padding, not Twig's format filter, which treats the value as a sprintf format string.
A malformed template never breaks checkout
A template that does not parse, reads a variable it may not, or fails while rendering is caught. The shipped default renders instead, and one log line per slot per process names the slot and the error:
kahunacart: the order_reference object template failed and the shipped format was used instead — Unexpected token "end of template"
Saving already refuses the first two and renders against the slot's sample to catch most of the third. If a template still fails on a live store, the order completes, the line is titled, the invoice downloads and the email goes out on the shipped formats.
A template formats a number, it never generates one
order_reference receives order.number after the sequence table has allocated it, inside the same transaction. sku_format receives seq the same way. No template is ever asked for a number, and nothing reads a number back out of what a template printed, so numbering stays gapless and safe under concurrency. A store that wants 2026-000042 writes that as a format of the allocated number.
The reference is rendered once, at completion, and frozen in the order's data. Change the format and only orders placed from then on carry the new one. An order placed before references existed has none stored and prints #<number>.
A yearly order reference
{{ order.date|date('Y') }}-{{ order.number|pad(6) }}
prints 2026-001042 for order 1042 completed in 2026.
To reset the invoice number each year, use invoice.reset_yearly. Order numbers never reset, so the year belongs in the format. See Configuration → Invoice numbering.
kahunacart_order_reference(order) in Twig prints the reference for any order, the order API carries it as reference, and every email_subject.* slot may read it as order.reference.
Where each slot is consumed
| Slot | Consumer |
|---|---|
sku_format |
The products admin API, for a variant created or updated with a blank SKU while the slot is not empty. The sku sequence is allocated only then |
product_title_format, variant_title_format |
The cart, at add-to-cart, snapshotted onto the line and never recomputed |
order_description |
The payment service, at every payment start, handed to the provider in PaymentContext::$metadata['description'] |
order_reference |
Checkout completion, written to the order's data as reference |
pdf_filename |
The invoice PDF download. Whatever the template prints is reduced to letters, digits, dots, dashes and underscores and given a .pdf |
email_subject.<code> |
Every order email's job handler, with the translated subject as default |