Skip to main content
This guide shows a practical checkout wizard flow:
  1. Shipping details
  2. Payment details (including card panel)
  3. Confirmation (redirect to your next page)
It uses the exact customer/cart field bindings recognized by the checkout runtime.

Step 0: Enable checkout mode for the page

Before adding fields, make sure this page is marked as a checkout page in ElasticFunnels:
  1. Go to Page List
  2. Click Edit Page on the page you are building
  3. Enable Is checkout page
  4. Save
Without this, checkout-specific behavior (totals, submit handling, payment processing, redirects) may not run as expected.

Step 1: Shipping details

Use name="<field>" and data-template-value="checkout.customer.<field>" together for each input/select.

Required fields validated by checkout

Labelnamedata-template-value example
First namefirst_namecheckout.customer.first_name
Last namelast_namecheckout.customer.last_name
Emailemailcheckout.customer.email
Street addressshipping_addresscheckout.customer.shipping_address
Countryshipping_countrycheckout.customer.shipping_country
Cityshipping_citycheckout.customer.shipping_city
State/Provinceshipping_statecheckout.customer.shipping_state
ZIP/Postal codeshipping_zipcheckout.customer.shipping_zip

Common optional fields

Labelnamedata-template-value example
Phone numberphonecheckout.customer.phone
Apt/Suiteshipping_address2checkout.customer.shipping_address2
Shipping first name (advanced fallback)shipping_first_namecheckout.customer.shipping_first_name
Shipping last name (advanced fallback)shipping_last_namecheckout.customer.shipping_last_name

Shipping section example

<section class="shipping-step">
  <h2>Shipping Information</h2>

  <div class="floating-label-group">
    <label>First name*</label>
    <input name="first_name" data-template-value="checkout.customer.first_name" type="text" />
    <div class="form-error" data-checkout-error="first_name"></div>
  </div>

  <div class="floating-label-group">
    <label>Last name*</label>
    <input name="last_name" data-template-value="checkout.customer.last_name" type="text" />
    <div class="form-error" data-checkout-error="last_name"></div>
  </div>

  <div class="floating-label-group">
    <label>Email*</label>
    <input name="email" data-template-value="checkout.customer.email" type="email" />
    <div class="form-error" data-checkout-error="email"></div>
  </div>

  <div class="floating-label-group">
    <label>Phone number</label>
    <input name="phone" data-template-value="checkout.customer.phone" type="text" />
    <div class="form-error" data-checkout-error="phone"></div>
  </div>

  <div class="floating-label-group">
    <label>Street address*</label>
    <input name="shipping_address" data-template-value="checkout.customer.shipping_address" type="text" />
    <div class="form-error" data-checkout-error="shipping_address"></div>
  </div>

  <div class="floating-label-group">
    <label>Apt/Suite (optional)</label>
    <input name="shipping_address2" data-template-value="checkout.customer.shipping_address2" type="text" />
    <div class="form-error" data-checkout-error="shipping_address2"></div>
  </div>

  <div class="floating-label-group">
    <label>Country*</label>
    <select name="shipping_country" data-template-value="checkout.customer.shipping_country">
      <option value="">Select Country</option>
      <option data-each="opt in checkout.countries" data-ef-value="opt.value">[[ opt.label ]]</option>
    </select>
    <div class="form-error" data-checkout-error="shipping_country"></div>
  </div>

  <div class="floating-label-group">
    <label>City*</label>
    <input name="shipping_city" data-template-value="checkout.customer.shipping_city" type="text" />
    <div class="form-error" data-checkout-error="shipping_city"></div>
  </div>

  <template-if data-condition="checkout.customer.shipping_country == 'US'">
    <div class="floating-label-group">
      <label>State*</label>
      <select name="shipping_state" data-template-value="checkout.customer.shipping_state">
        <option value="">Select State</option>
        <option data-each="opt in checkout.usStates" data-ef-value="opt.value">[[ opt.label ]]</option>
      </select>
      <div class="form-error" data-checkout-error="shipping_state"></div>
    </div>
  </template-if>

  <template-if data-condition="checkout.customer.shipping_country != 'US'">
    <div class="floating-label-group">
      <label>State/Province*</label>
      <input name="shipping_state" data-template-value="checkout.customer.shipping_state" type="text" />
      <div class="form-error" data-checkout-error="shipping_state"></div>
    </div>
  </template-if>

  <div class="floating-label-group">
    <label>ZIP / Postal code*</label>
    <input name="shipping_zip" data-template-value="checkout.customer.shipping_zip" type="text" />
    <div class="form-error" data-checkout-error="shipping_zip"></div>
  </div>
</section>

checkout.countries and checkout.usStates (auto-provided)

These lists are automatically provided by the platform on checkout pages:
  • checkout.countries: full country list for country dropdowns
  • checkout.usStates: US states list for US shipping/billing state dropdowns
So you do not need to manually define all countries/states in your page code.

Show all countries (default)

<select name="shipping_country" data-template-value="checkout.customer.shipping_country">
  <option value="">Select Country</option>
  <option data-each="opt in checkout.countries" data-ef-value="opt.value">[[ opt.label ]]</option>
</select>

Limit the countries you display

You can limit visible countries by filtering inside the loop:
<select name="shipping_country" data-template-value="checkout.customer.shipping_country">
  <option value="">Select Country</option>
  <template-foreach data-each="opt in checkout.countries">
    <template-if data-condition="opt.value == 'US' || opt.value == 'CA' || opt.value == 'GB'">
      <option value="[[ opt.value ]]">[[ opt.label ]]</option>
    </template-if>
  </template-foreach>
</select>
If you only show a subset in the dropdown, customers can only choose from that subset.

Step 2: Billing + payment details

Shipping same as billing toggle

Use this exact binding:
<label>
  <input
    type="checkbox"
    name="shipping_same_as_billing"
    checked
    data-template-value="checkout.shipping_same_as_billing"
  />
  Shipping details same as billing
</label>
Show billing fields only when the checkbox is off:
<template-if data-condition="checkout.shipping_same_as_billing == false">
  <!-- billing fields -->
</template-if>

Billing field keys (when visible)

Labelnamedata-template-value example
First namebilling_first_namecheckout.customer.billing_first_name
Last namebilling_last_namecheckout.customer.billing_last_name
Street addressbilling_addresscheckout.customer.billing_address
Apt/Suitebilling_address2checkout.customer.billing_address2
Countrybilling_countrycheckout.customer.billing_country
Citybilling_citycheckout.customer.billing_city
State/Provincebilling_statecheckout.customer.billing_state
ZIP/Postal codebilling_zipcheckout.customer.billing_zip
If billing is empty, checkout uses shipping values automatically.

How data-checkout-error works (not magic)

data-checkout-error is the target placeholder where checkout validation messages are rendered. Use matching keys between field name and error placeholder:
<input name="email" data-template-value="checkout.customer.email" type="email" />
<div class="form-error" data-checkout-error="email"></div>
What happens on submit:
  • Checkout validates required/visible fields
  • If invalid, checkout writes messages into checkout.errors
  • The frontend template processor + checkout error adapter update matching [data-checkout-error="<field>"] containers
  • The first invalid field is focused automatically
If no matching data-checkout-error element exists, the field can still fail validation, but users will not see an inline message for that field. For card payments, use:
<checkout-cc-panel
  data-accepted-cards="visa,mastercard,amex,discover"
  data-show-security-info="true"
></checkout-cc-panel>
Add a submit button and message container:
<div data-checkout-message class="checkout-message"></div>
<button type="submit" data-checkout-submit>PAY NOW</button>

Payment method switch (optional)

If you show multiple methods, use payment_method_choice:
<label><input type="radio" name="payment_method_choice" value="card" checked /> Card</label>
<label><input type="radio" name="payment_method_choice" value="paypal" /> PayPal</label>
<label><input type="radio" name="payment_method_choice" value="klarna" /> Klarna</label>
<label>
  <input type="checkbox" name="allow_emails" />
  Yes, send me promotions and updates
</label>

Step 3: Confirmation (redirect step)

After submit:
  • If checkout returns redirectUrl, customer is redirected there.
  • Otherwise, customer is redirected to /purchase?order_id=...&email=....
So your “confirmation step” in a wizard is typically a redirect to:
  • your thank-you page
  • a next funnel step (upsell/downsell)
  • a custom post-purchase page

How field bindings are handled

Checkout forms are reactive through the frontend template processor (same engine used by [[ ... ]], <template-if>, and <template-foreach>).
  • data-template-value="checkout.customer.email" creates two-way binding:
    • scope -> input (prefill/update)
    • input -> scope (user typing updates checkout data)
  • <template-if data-condition="..."> reacts when bound values change (for example, country/state or billing visibility)
  • data-checkout-submit wires the button to checkout submit + payment flow
  • data-checkout-message is where global submit/payment messages are shown
Use these references if you want deeper engine behavior details:

Cart + order summary fields from cart.js

Use these fields to build the right-side summary and item list.

Item fields (cartItems / items)

FieldUse in template
item.codeProduct code for qty/remove buttons
item.nameProduct title
item.imageProduct image
item.quantityQuantity
item.priceUnit price (formatted)
item.retail_priceRetail/original unit price (formatted)
item.totalLine total (formatted)
item.item_total_raw / item.total_rawNumeric line total
item.discount_totalLine discount (formatted)
item.currencyCurrency code

Scope-level cart fields

FieldMeaning
cartItems / itemsCurrent cart lines
countTotal quantity across items
item_countNumber of line items
is_single_productSingle-product checkout mode
is_cartMulti-item cart mode
currencyCheckout currency

Checkout totals fields

cart.js keeps these formatted fields synced from their *_raw values:
  • checkout.subtotal
  • checkout.total
  • checkout.tax
  • checkout.shipping
  • checkout.discount_total
  • checkout.bump_total
  • checkout.product_price
  • checkout.retail_price

Cart control attributes (quantity/remove)

Use these exact attributes:
<template-foreach data-each="item in cartItems">
  <button type="button" data-cart-qty-minus data-code="[[ item.code ]]" data-disable-when-one>-</button>
  <span>[[ item.quantity ]]</span>
  <button type="button" data-cart-qty-plus data-code="[[ item.code ]]">+</button>
  <button type="button" data-cart-qty-remove data-code="[[ item.code ]]">Remove</button>
</template-foreach>

Complete wizard skeleton (shipping -> payment -> redirect)

<form id="checkout-form">
  <!-- Step 1 -->
  <section id="step-shipping">...</section>

  <!-- Step 2 -->
  <section id="step-payment">
    <template-if data-condition="checkout.shipping_same_as_billing == false">
      <!-- billing fields -->
    </template-if>

    <checkout-cc-panel data-accepted-cards="visa,mastercard,amex,discover"></checkout-cc-panel>
    <div data-checkout-message class="checkout-message"></div>
    <button type="submit" data-checkout-submit>Pay now</button>
  </section>

  <!-- Step 3 -->
  <!-- No extra markup required: successful checkout redirects automatically -->
</form>

Quick QA checklist

  • Required shipping/personal fields use name + data-template-value.
  • Every required field has matching data-checkout-error="<field>".
  • Country/state uses checkout.countries / checkout.usStates.
  • Card panel is present and submit button uses data-checkout-submit.
  • Optional methods use payment_method_choice values (card, paypal, klarna).
  • Cart controls use data-cart-qty-minus, data-cart-qty-plus, data-cart-qty-remove, data-code.
  • Success and decline redirects are tested end-to-end.