Skip to main content
This page covers limitations of the frontend template engine (compared to full frameworks like Vue or Alpine) and common pitfalls with fixes.

Quick reference: common pitfalls


Limitations

Not a full framework

  • No virtual DOM — Updates are reactive and path-scoped, but this is not a component framework with diffed virtual DOM.
  • Element-level lifecycle only — You have @mounted, @unmounted, and @visible per element (see Bindings & Events), plus window.ef.template.watch(...) and window.ef.template.computed(...) for scope-driven reactions. There is no full component composition API (no slots, no scoped components, no props).
  • No standalone template-else — A <template-else> must be a sibling of a <template-if> (or follow template-else-if). You cannot have “else only” without an if.

Reactivity model

  • window.efScope is reactive — changing nested keys/arrays updates bound UI automatically.
  • Path-scoped updates still exist — internals and advanced callers can use path-scoped updates for high-frequency flows; most custom code does not need manual notify calls.

Expression evaluation

  • Expressions run in a merged context (scope + loop vars) using with(scope) { return (expression); }. Very complex expressions or ones that rely on strict mode / undeclared variables can be fragile. Prefer simple paths and small expressions.
  • Reserved names in event handlers: $event, event, $el, $target are injected; avoid using them as scope keys if you need to read them from global scope.

Checkout and cart data

  • checkout is only set on checkout pages. Any template that uses checkout.* on a non-checkout page may see undefined. Guard with <template-if data-condition="checkout"> in shared components.
  • query (URL params) is only available if the app sets efScope.query at init (e.g. via syncQueryToScope()). Conditions like query.coupon will not work otherwise.

Common pitfalls

1. Using formatPrice on already-formatted values

Mistake: [[ formatPrice(checkout.total, checkout.currency) ]] or [[ formatPrice(item.price, item.currency) ]]. Why it’s wrong: checkout.total, item.price, item.total, bump.price, line.price are already formatted currency strings. Passing them to formatPrice can produce wrong or duplicated symbols. Fix: Use [[ checkout.total ]], [[ item.price ]], [[ item.total ]], [[ bump.price ]], [[ line.price ]] as-is. Use formatPrice only for raw values (e.g. checkout.total_raw, bump.price_raw) when you need to display a number.

2. Using {{ }} in frontend templates

Mistake: Writing {{ variable }} in HTML that is processed by the frontend engine. Why it’s wrong: {{ }} is reserved for the backend Template Engine. The frontend engine only replaces [[ ]]. Fix: Use [[ expression ]] for frontend token interpolation. For attribute bindings, prefer ef-*/data-ef-* (e.g. ef-text="checkout.total").
Mistake: Changing one path but binding to another (for example mutating checkout.items while template reads cartItems, or expecting item.quantity aliases outside foreach context).Why it breaks: Reactivity runs, but your binding expression may not reference the changed path or expected data shape.Fix: Keep data keys and template expressions aligned (e.g. cartItems with item in cartItems and item.* inside that loop). Inspect window.efScope and Pick Element bindings in the Templates debug tab.
Mistake: In the single-product layout (one product, big image + title), using cartItems[0].name or cartItems[0].image.Why it’s fragile: The app may not guarantee cartItems[0] on single-product checkout; the canonical data for that view is checkout.product_title, checkout.product_image, etc.Fix: Use <template-if data-condition="is_single_product"> and checkout.product_* keys; use cartItems only in the cart block. See Checkout — Order summary.
Mistake: No data-ef-cloak on blocks that contain [[ ... ]] or template-if content, so users see raw brackets or the wrong branch until the processor runs.Fix: Add data-ef-cloak to elements that should be hidden until the template engine has run. The engine removes this attribute after processing. On ElasticFunnels-hosted pages the cloak stylesheet is already in <head> and hides [data-ef-cloak] plus every engine tag — template-if, template-else-if, template-if-else, template-else, template-foreach, template-vars, template-set, template-partial / partial. If you embed the engine elsewhere, hide that full list; a tag left out of it paints its raw content on first paint and then disappears.
Mistake: Putting <template-else> inside <template-if>, or using a tag like <template-if-else>.Fix: template-else and template-else-if must be siblings of template-if, immediately after it. Structure:
Mistake: Checkout inputs without data-checkout-error="fieldId" container, or id / data-template-value not matching the field list the backend expects.Fix: Each checkout field needs id, name, data-template-value, and <div class="form-error" data-checkout-error="<id>"></div>. Use exact ids and paths from the spec. See Checkout — Form fields.
Mistake: Wrapping <option> elements in <template-foreach> inside a <select>:
Why it breaks: Safari and iOS strip unknown/custom elements from <select>. The <template-foreach> tag is removed by the browser before the template engine runs, so no options render.Fix: Use inline data-each directly on the <option> tag:
The server-side processor auto-converts <template-foreach> inside <select> in page templates, but client-side generated HTML (e.g. plugins) must use the inline form directly.
Mistake: Bump checkboxes/labels without data-bump-checkbox, data-bump-toggle, or data-bump-code, so the bump-products plugin cannot toggle or style them.Fix: Use data-bump-code="[[ bump.code ]]" on the card, data-bump-checkbox="[[ bump.code ]]" and value="[[ bump.code ]]" on the checkbox, and data-bump-toggle="[[ bump.code ]]" on the label. Same code on both “add” and “remove” states.

Summary

  • Limitations: No virtual DOM/component lifecycle; template-else needs a sibling template-if; checkout and query are context-specific.
  • Pitfalls: Don’t use formatPrice on formatted values; use [[ ]] not for frontend; keep data paths aligned with bindings; use checkout.* for single-product summary; use data-ef-cloak where needed; keep template-if/else structure and checkout/bump attributes as specified; never use template-foreach inside select — use inline data-each on option instead (Safari/iOS).
Next: Debugging.