Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.elasticfunnels.io/llms.txt

Use this file to discover all available pages before exploring further.

This guide walks through building checkout pages with the page builder or code editor. For exact semantics of checkout_settings, scope keys, and security options, see Checkout functionality reference. For template syntax details, see Frontend Template Engine: Checkout.

Prerequisites

  1. The page must have “Is checkout page” enabled (Page List → Edit Page → toggle on).
  2. A product must be linked via a buy link or cart.
  3. The domain must have a merchant assigned (Settings → Domains → Edit → Merchant).
Quiz pages that process payments must also have “Is checkout page” enabled. Quiz functionality and checkout are independent page flags — enabling both is valid and expected for quiz-to-purchase flows.

Example 1: Standard checkout

The most common layout: shipping address at the top, billing (same as shipping by default), payment panel with card form and wallet buttons, then a pay button.

Backend script

<script scope="backend">
var checkout_settings = checkout_settings || {};
checkout_settings.countries = ['US', 'CA', 'GB', 'AU'];
</script>

Template

<form id="checkout-form" method="POST">

  <!-- Contact -->
  <section class="shipping-info-card">
    <div class="form-grid">
      <div class="floating-label-group">
        <label class="form-label">Phone Number *</label>
        <input type="text" name="phone" data-template-value="checkout.customer.phone" class="form-input" />
        <div class="form-error" data-checkout-error="phone"></div>
      </div>
      <div class="floating-label-group">
        <label class="form-label">Email Address *</label>
        <input type="email" name="email" data-template-value="checkout.customer.email" class="form-input" />
        <div class="form-error" data-checkout-error="email"></div>
      </div>
    </div>
  </section>

  <!-- Shipping address -->
  <section class="shipping-info-card">
    <h2 class="section-title">Shipping Information</h2>
    <div class="form-grid">
      <div class="floating-label-group">
        <label class="form-label">Street Address *</label>
        <input type="text" name="shipping_address"
               data-template-value="checkout.customer.shipping_address"
               autocomplete="shipping street-address" class="form-input" />
        <div class="form-error" data-checkout-error="shipping_address"></div>
      </div>
      <div class="floating-label-group">
        <label class="form-label">Apt/Suite (Optional)</label>
        <input type="text" name="shipping_address2"
               data-template-value="checkout.customer.shipping_address2"
               autocomplete="shipping address-line2" class="form-input" />
      </div>
      <div class="floating-label-group">
        <label class="form-label">City *</label>
        <input type="text" name="shipping_city"
               data-template-value="checkout.customer.shipping_city"
               autocomplete="shipping address-level2" class="form-input" />
        <div class="form-error" data-checkout-error="shipping_city"></div>
      </div>
      <div class="floating-label-group">
        <label class="form-label">State/Province *</label>
        <select name="shipping_state"
                data-template-value="checkout.customer.shipping_state"
                autocomplete="shipping address-level1" class="form-input">
          <option value="">Select State</option>
          <template-foreach data-each="opt in checkout.usStates">
            <option value="[[ opt.value ]]">[[ opt.label ]]</option>
          </template-foreach>
        </select>
        <div class="form-error" data-checkout-error="shipping_state"></div>
      </div>
      <div class="floating-label-group">
        <label class="form-label">Postal Code *</label>
        <input type="text" name="shipping_zip"
               data-template-value="checkout.customer.shipping_zip"
               autocomplete="shipping postal-code" class="form-input" />
        <div class="form-error" data-checkout-error="shipping_zip"></div>
      </div>
      <input type="hidden" name="shipping_country"
             data-template-value="checkout.customer.shipping_country" />
    </div>

    <!-- Same as billing toggle -->
    <label class="checkbox-label">
      <input type="checkbox" checked
             data-template-value="checkout.shipping_same_as_billing"
             data-set-onload class="checkbox-input" />
      <span class="checkbox-text">Shipping details same as billing</span>
    </label>
  </section>

  <!-- Conditional billing (shown when unchecked) -->
  <template-if data-condition="checkout.shipping_same_as_billing == false">
    <section class="billing-info-card">
      <h2 class="section-subtitle">Billing Information</h2>
      <template-if data-condition="!checkout.shipping_same_as_billing">
        <div class="form-grid">
          <div class="floating-label-group">
            <label class="form-label">Full Name *</label>
            <input type="text" name="billing_first_name"
                   data-template-value="checkout.customer.billing_first_name" class="form-input" />
            <div class="form-error" data-checkout-error="billing_first_name"></div>
          </div>
          <div class="floating-label-group">
            <label class="form-label">Street Address *</label>
            <input type="text" name="billing_address"
                   data-template-value="checkout.customer.billing_address" class="form-input" />
            <div class="form-error" data-checkout-error="billing_address"></div>
          </div>
          <div class="floating-label-group">
            <label class="form-label">City *</label>
            <input type="text" name="billing_city"
                   data-template-value="checkout.customer.billing_city" class="form-input" />
            <div class="form-error" data-checkout-error="billing_city"></div>
          </div>
          <div class="floating-label-group">
            <label class="form-label">State/Province *</label>
            <select name="billing_state"
                    data-template-value="checkout.customer.billing_state" class="form-input">
              <option value="">Select State</option>
              <template-foreach data-each="opt in checkout.usStates">
                <option value="[[ opt.value ]]">[[ opt.label ]]</option>
              </template-foreach>
            </select>
            <div class="form-error" data-checkout-error="billing_state"></div>
          </div>
          <div class="floating-label-group">
            <label class="form-label">Postal Code *</label>
            <input type="text" name="billing_zip"
                   data-template-value="checkout.customer.billing_zip" class="form-input" />
            <div class="form-error" data-checkout-error="billing_zip"></div>
          </div>
        </div>
      </template-if>
      <template-else>
        <p class="billing-info-message">Billing address same as shipping.</p>
        <input type="hidden" name="billing_first_name" data-template-value="checkout.customer.billing_first_name" />
        <input type="hidden" name="billing_address" data-template-value="checkout.customer.billing_address" />
        <input type="hidden" name="billing_city" data-template-value="checkout.customer.billing_city" />
        <input type="hidden" name="billing_state" data-template-value="checkout.customer.billing_state" />
        <input type="hidden" name="billing_zip" data-template-value="checkout.customer.billing_zip" />
      </template-else>
      <input type="hidden" name="billing_country" data-template-value="checkout.customer.billing_country" />
    </section>
  </template-if>

  <!-- Order summary -->
  <template-if data-condition="is_single_product">
    <div class="order-summary-single">
      <img ef-src="checkout.product_image" ef-alt="checkout.product_title" />
      <h2><span ef-text="checkout.product_title">Product Title</span></h2>
      <span ef-text="checkout.product_price">$0.00</span>
    </div>
  </template-if>

  <!-- Totals -->
  <div class="price-summary-section">
    <div class="price-breakdown-row">
      <span>Subtotal</span>
      <span ef-text="checkout.subtotal_after_discount">$0.00</span>
    </div>
    <template-if data-condition="checkout.shipping_raw > 0">
      <div class="price-breakdown-row">
        <span>Shipping</span>
        <span ef-text="checkout.shipping">FREE</span>
      </div>
    </template-if>
    <template-if data-condition="checkout.tax_raw > 0">
      <div class="price-breakdown-row">
        <span>Tax</span>
        <span ef-text="checkout.tax">$0.00</span>
      </div>
    </template-if>
    <div class="total-price-row">
      <span class="total-label">Total</span>
      <span ef-text="checkout.total">$0.00</span>
    </div>
  </div>

  <!-- Payment panel (card + wallet buttons injected automatically) -->
  <checkout-cc-panel></checkout-cc-panel>
  <div data-checkout-message class="checkout-message"></div>

  <!-- Submit -->
  <button type="submit" data-checkout-submit form="checkout-form"
          class="pay-now-button">
    <span class="pay-now-text">PAY NOW</span>
  </button>

</form>
Apple Pay and Google Pay buttons appear automatically in the payment panel when available. When a customer pays with a wallet, the correct billing address from the wallet is used for payment verification — no special handling needed in the template.

Example 2: Wallet-first checkout

For quiz funnels or lightweight pages where you want wallet buttons prominently at the top. This uses the same blocks and backend processing as the standard checkout — the only difference is layout order.

Backend script

<script scope="backend">
var checkout_settings = checkout_settings || {};
checkout_settings.countries = ['US', 'CA'];
// Optional: subscription wallet labels
checkout_settings.wallet = {
  recurring_description: 'Monthly wellness subscription',
  management_url: 'https://members.yourdomain.com/subscriptions'
};
</script>

Template

<form id="checkout-form" method="POST">

  <!-- Contact (minimal: email + phone first) -->
  <section class="shipping-info-card">
    <div class="form-grid">
      <div class="floating-label-group">
        <label class="form-label">Email Address *</label>
        <input type="email" name="email" data-template-value="checkout.customer.email"
               autocomplete="email" class="form-input" />
        <div class="form-error" data-checkout-error="email"></div>
      </div>
      <div class="floating-label-group">
        <label class="form-label">Phone Number *</label>
        <input type="text" name="phone" data-template-value="checkout.customer.phone"
               class="form-input" />
        <div class="form-error" data-checkout-error="phone"></div>
      </div>
    </div>
  </section>

  <!-- Payment panel EARLY (wallet buttons appear here, above shipping) -->
  <checkout-cc-panel></checkout-cc-panel>
  <div data-checkout-message class="checkout-message"></div>

  <!-- Shipping address (below payment) -->
  <section class="shipping-info-card">
    <h2 class="section-title">Shipping Information</h2>
    <div class="form-grid">
      <div class="floating-label-group">
        <label class="form-label">Street Address *</label>
        <input type="text" name="shipping_address"
               data-template-value="checkout.customer.shipping_address"
               autocomplete="shipping street-address" class="form-input" />
        <div class="form-error" data-checkout-error="shipping_address"></div>
      </div>
      <div class="floating-label-group">
        <label class="form-label">City *</label>
        <input type="text" name="shipping_city"
               data-template-value="checkout.customer.shipping_city"
               autocomplete="shipping address-level2" class="form-input" />
        <div class="form-error" data-checkout-error="shipping_city"></div>
      </div>
      <div class="floating-label-group">
        <label class="form-label">State/Province *</label>
        <select name="shipping_state"
                data-template-value="checkout.customer.shipping_state"
                autocomplete="shipping address-level1" class="form-input">
          <option value="">Select State</option>
          <template-foreach data-each="opt in checkout.usStates">
            <option value="[[ opt.value ]]">[[ opt.label ]]</option>
          </template-foreach>
        </select>
        <div class="form-error" data-checkout-error="shipping_state"></div>
      </div>
      <div class="floating-label-group">
        <label class="form-label">Postal Code *</label>
        <input type="text" name="shipping_zip"
               data-template-value="checkout.customer.shipping_zip"
               autocomplete="shipping postal-code" class="form-input" />
        <div class="form-error" data-checkout-error="shipping_zip"></div>
      </div>
      <input type="hidden" name="shipping_country"
             data-template-value="checkout.customer.shipping_country" />
    </div>
    <label class="checkbox-label">
      <input type="checkbox" checked
             data-template-value="checkout.shipping_same_as_billing"
             data-set-onload class="checkbox-input" />
      <span class="checkbox-text">Billing same as shipping</span>
    </label>
  </section>

  <!-- Totals -->
  <div class="price-summary-section">
    <div class="total-price-row">
      <span class="total-label">Total</span>
      <span ef-text="checkout.total">$0.00</span>
    </div>
  </div>

  <!-- Submit -->
  <button type="submit" data-checkout-submit form="checkout-form"
          class="pay-now-button">
    <span class="pay-now-text">COMPLETE ORDER</span>
  </button>

</form>
The key difference is <checkout-cc-panel> is placed above the shipping address section. Customers with Apple Pay or Google Pay see wallet buttons immediately after entering email/phone. Customers without wallet support see the card form there instead and fill in shipping below.
Both examples use the same checkout-details, checkout-cc-panel, and checkout-button patterns. The backend processes them identically. The only difference is where the blocks appear in the template.

Example 3: Minimal digital product checkout

For digital products where you only need email and payment — no name or address fields. The card panel automatically adds a cardholder name field and billing details since they’re missing from the page.

Template

<form id="checkout-form" method="POST">
  <section class="shipping-info-card">
    <div class="floating-label-group">
      <label class="form-label">Email Address *</label>
      <input type="email" name="email" data-template-value="checkout.customer.email"
             autocomplete="email" class="form-input" />
      <div class="form-error" data-checkout-error="email"></div>
    </div>
  </section>

  <checkout-cc-panel data-accepted-cards="visa,mastercard,amex,discover"
                     data-applepay-layout="separated"></checkout-cc-panel>

  <div data-checkout-message class="checkout-message"></div>
  <button type="submit" data-checkout-submit class="checkout-button">Purchase</button>
</form>
What happens automatically:
  • Apple Pay appears above the card panel with an “OR PAY ANOTHER WAY” divider. Since only email is on the page, the wallet only requests email — no address sheet.
  • Card panel shows cardholder name (since first_name/last_name aren’t on the page) and billing details (name, country, address).
  • Billing values are automatically copied to shipping during submission.

Configuration options

These are set via checkout_settings in a <script scope="backend"> block. See Checkout functionality reference for full details.
SettingPurpose
checkout_settings.countriesRestrict country dropdown (e.g. ['US', 'CA'])
checkout_settings.usStatesRestrict US state dropdown (e.g. ['CA', 'TX'])
checkout_settings.abuse_protectionCard-testing / velocity protection
checkout_settings.wallet.recurring_descriptionLabel on Apple Pay sheet for subscriptions
checkout_settings.wallet.management_urlSubscription management URL for Apple Pay

Page builder vs code editor

Both examples above show raw HTML for the code editor. In the page builder, use the Checkout block category:
  • Checkout Details — contact + shipping + billing (same as Example 1 contact/shipping sections)
  • Credit Card Form — equivalent to <checkout-cc-panel>
  • Checkout Button — equivalent to <button data-checkout-submit>
  • Price Summary — totals section
  • Checkout Coupon — coupon input with apply/clear
  • Checkout Bumps — order bump checkboxes
  • Single Product Summary — product image/title for single-product checkout
  • Cart Products — product list for multi-item checkout
Drag these blocks in your preferred order. For a wallet-first layout, place Credit Card Form above Checkout Details.