Skip to main content
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.