> ## 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.

# How to design a checkout page

> Step-by-step guide with copy-paste HTML examples for standard and wallet-first checkout layouts.

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](/checkouts/checkout-functionality-reference). For template syntax details, see [Frontend Template Engine: Checkout](/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](/products/buy-links) or cart.
3. The domain must have a **merchant** assigned (Settings → Domains → Edit → Merchant).

<Info>
  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.
</Info>

***

## 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

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

### Template

```html theme={null}
<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

```html theme={null}
<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

```html theme={null}
<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.

<Info>
  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.
</Info>

***

## 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

```html theme={null}
<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](/checkouts/checkout-functionality-reference#4-checkout_settings-backend-script) for full details.

| Setting                                          | Purpose                                          |
| ------------------------------------------------ | ------------------------------------------------ |
| `checkout_settings.countries`                    | Restrict country dropdown (e.g. `['US', 'CA']`)  |
| `checkout_settings.usStates`                     | Restrict US state dropdown (e.g. `['CA', 'TX']`) |
| `checkout_settings.abuse_protection`             | Card-testing / velocity protection               |
| `checkout_settings.wallet.recurring_description` | Label on Apple Pay sheet for subscriptions       |
| `checkout_settings.wallet.management_url`        | Subscription 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**.

***

## Related docs

* [Checkout functionality reference](/checkouts/checkout-functionality-reference) — scope keys, `checkout_settings`, abuse protection, submit flow
* [Frontend Template Engine: Checkout](/frontend-template-engine/checkout) — `checkout.*` bindings and template patterns
* [Apple Pay and Google Pay](/merchants/apple-pay-google-pay) — wallet setup and configuration
* [Buy links](/products/buy-links) — linking products to checkout
* [Checkout redirect behavior](/funnels/checkout-redirect-behavior) — post-checkout routing
