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
- The page must have “Is checkout page” enabled (Page List → Edit Page → toggle on).
- A product must be linked via a buy link or cart.
- 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.
| 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.