All expressions in the frontend template engine are evaluated against a merged context: the global scope (window.efScope) plus any local context (e.g. loop variables). This page describes what data lives where and how to use it safely.
Global scope: window.efScope
The single source of truth for reactive data is window.efScope. The application and plugins (cart, checkout, bumps) populate and update it.
- Initialization: The template engine ensures
window.efScope exists when initTemplateScope() runs (e.g. from main.js). Cart and checkout plugins then set cartItems, checkout, etc.
- Updates:
efScope is reactive. After code changes efScope, the DOM updates automatically.
Typical top-level keys
| Key | Description |
|---|
cartItems (or items) | Array of cart line items. Prefer cartItems in new templates. |
checkout | Checkout data: totals, customer fields, bump products, coupon, etc. Only set on checkout pages. |
query | URL query parameters as an object (e.g. query.coupon, query.p, query.bumps). Must be populated at init (e.g. via syncQueryToScope()). |
currency | Default currency (e.g. USD) when not overridden by checkout.currency. |
is_cart / is_single_product | Booleans set by the app to distinguish multi-item checkout vs single-product checkout. |
errors | Object keyed by field id: { [fieldId]: message }. Used for checkout validation messages. |
mode | Helper object with e.g. item_count, has_query_product, is_cart, is_single_product. |
checkout is only defined on checkout pages. On other pages, checkout may be undefined. Guard with <template-if data-condition="checkout"> if you use checkout.* in shared templates.
Cart: cartItems / items
cartItems — Preferred name for the array of cart line items (used on checkout and cart UI).
items — Alias; same data in many contexts.
- On checkout pages the same list is also exposed as
checkout.cart and checkout.items.
Each item typically has:
| Property | Description |
|---|
code | Product/cart line code (used for qty +/- and remove). |
name | Product name. |
image | Product image URL (for thumbnails). |
quantity | Quantity. |
price | Unit price — already formatted (e.g. “$29.00”). Use as-is in [[ item.price ]]. |
total | Line total — already formatted. Use [[ item.total ]]; do not call formatPrice(lineTotal(item), ...). |
currency | Currency code for the line. |
Cart plugin (e.g. cart.js) builds this structure and provides formatted price and total; the template engine does not re-format them.
Checkout: checkout.*
On checkout pages, efScope.checkout holds everything needed for the order summary, form, and payment. For full checkout-specific docs (totals, fields, coupon, bumps, payment panel), see Checkout.
The same totals schema is available in both checkout modes (is_single_product and is_cart), so templates can reuse one price-summary layout.
Formatted keys (display):
checkout.product_price, checkout.retail_price
checkout.bump_total
checkout.subtotal_before_discount
checkout.discount_total
checkout.subtotal_after_discount
checkout.subtotal (legacy alias of subtotal_after_discount)
checkout.shipping, checkout.tax, checkout.total
Raw keys (conditions/math):
checkout.product_price_raw, checkout.retail_price_raw
checkout.bump_total_raw
checkout.subtotal_before_discount_raw
checkout.discount_total_raw
checkout.subtotal_after_discount_raw
checkout.subtotal_raw (legacy alias of subtotal_after_discount_raw)
checkout.shipping_raw, checkout.tax_raw, checkout.total_raw
Use formatted keys directly in templates; use raw keys for conditions like checkout.tax_raw > 0.
Single-product vs cart layout
is_single_product — One product; show hero image + title block. Use checkout.product_image, checkout.product_title, checkout.product_price, checkout.retail_price (and optionally product_code, product_description). Do not use cartItems[0] in that block.
is_cart — Multiple items; show list with <template-foreach data-each="item in cartItems"> and [[ item.image ]], [[ item.name ]], [[ item.total ]], etc.
Bumps
checkout.bump_products — Full list of bump products (each has code, name, description, price formatted, price_raw numeric, image URL, added boolean).
checkout.selectedBumpLines — Array of selected bumps: code, name, price (formatted), price_raw.
Use [[ bump.price ]] and [[ line.price ]] for display; do not pass them to formatPrice. Use price_raw only for conditions or when you explicitly format a raw number. Use bump.added for conditional blocks and for @click add/remove patterns (e.g. <button @click="bump.added = true"> / <button @click="bump.added = false">); use ef-src="bump.image" for the bump image (with a placeholder src).
Coupon
checkout.coupon — When applied: applied, code, name, value_formatted, fixed_value, pct_value, discount.
checkout.coupon_message — Error message from validation (e.g. invalid code). Show with <template-if data-condition="checkout.coupon_message"> and [[ checkout.coupon_message ]].
- URL coupon: Use
query.coupon to detect a coupon from the URL; use checkout.coupon for the applied details after the app auto-applies.
checkout.customer.* — Paths used with data-template-value for two-way binding: e.g. checkout.customer.email, checkout.customer.shipping_first_name, checkout.customer.billing_first_name.
checkout.shipping_same_as_billing — Boolean; when true, shipping address block can be hidden and a short message shown instead.
checkout.countries, checkout.usStates — Arrays of { value, label } for country/state <select> options.
URL parameters: query.*
query should be an object built from window.location.search (e.g. via syncQueryToScope() at init). Each query parameter is a key; e.g. query.coupon, query.p, query.bumps.
Use query.* for URL-driven visibility:
<template-if data-condition="query.coupon">
<p>Automatically applied coupon: [[ checkout.coupon.code ]]</p>
</template-if>
If the app does not set efScope.query, conditions like query.coupon will be undefined and the block will not show.
Effective context and mode
The engine merges caller context (e.g. from a loop) with window.efScope to form the effective context for each expression. It also adds:
mode — Object with item_count, has_query_product, is_cart, is_single_product.
is_cart, is_single_product — Convenience booleans on the root of the merged context.
So you can write <template-if data-condition="is_single_product"> and <template-if data-condition="is_cart"> without touching checkout directly.
Runtime API: watch and computed
The frontend API also exposes Vue-like helpers at window.ef.template:
const stop = window.ef.template.watch('checkout.total', (newValue, oldValue) => {
console.log('total changed', oldValue, '->', newValue);
}, { immediate: true });
const searchUrl = window.ef.template.computed('links.googleSearch', (ctx) => {
const q = ctx?.search ?? '';
return `https://google.com/?s=${encodeURIComponent(q)}`;
});
// use in template:
// <a ef-href="links.googleSearch">Search</a>
// cleanup:
stop();
searchUrl.stop();
Summary
- Data lives in
window.efScope; expressions see global scope + loop/block context.
- Reactivity is automatic when
efScope values change.
- Cart: Use
cartItems (or items); each item has price, total (formatted), code, name, image, quantity.
- Checkout: Use
checkout.* for totals, bumps, coupon, customer; use formatted keys for display and _raw only when you need numbers or formatPrice.
- URL: Use
query.* after the app has set efScope.query (e.g. via syncQueryToScope()).
- Layout: Use
is_single_product and is_cart to choose between single-product summary and cart list.
Next: Bindings & Events.