Checkout Form (tag pair)
checkout_form outputs an HTML checkout form that posts to checkout_action, saves customer data, validates required fields, and starts payment processing.
A video tutorial on this tag is available.
See also:
- Checkout Amounts and Payment Review
- Custom checkout JavaScript
- gateway_fields
- selected_gateway_fields
- gateway_fields_url
- gateway_select
- submitted_order_info
- checkout_price_change
- global_form_settings
Parameters
In addition to the parameters listed here, this tag also supports Global Form Parameters such as return, secure_return, error_handling, show_errors, required, rules:*, action, secure_action, id, class, name, onsubmit, enctype, and logged_out_redirect.
Amount source and override precedence
Without amount-changing parameters, checkout uses the current product-entry data, cart state, customer information, and store configuration. Mapped product settings—including price, per-product shipping, modifiers, and tax/shipping exemptions—are evaluated again when checkout is finalized; they are not necessarily frozen when the item enters the cart.
Recognized amount and exemption parameters on checkout_form override
those calculated settings for this checkout. In particular:
pricereplaces the calculated order subtotal;shippingreplaces calculated shipping;taxreplaces calculated tax;no_shipping/shipping_exemptremoves calculated shipping;no_tax/tax_exemptremoves calculated tax;- permitted
allow_user_shippinginput replaces shipping; - permitted
allow_user_priceinput replaces the final payable total.
Explicit numeric shipping and tax parameters take precedence over
the corresponding exemption parameters. These overrides affect the
finalized order calculation; they do not change the product entry or
bypass inventory validation.
- required
- cart_empty_redirect
- gateway
- gateway_method
- price
- allow_user_price
- shipping
- allow_user_shipping
- tax
- no_tax / tax_exempt
- no_shipping / shipping_exempt
- country_code
- expiration_date
- create_user
- group_id
- force_processing
- force_vault
- vault_id
- member_id
- order_id
- authorized_redirect / failed_redirect / declined_redirect / processing_redirect
- custom_js
- price_change_handling
- price_change_template
required
Adds required validation rules for additional fields on this checkout form.
Use a pipe-delimited list:
required="first_name|last_name|email_address"
Gateway-required fields are validated automatically. To exclude specific gateway-required fields, use the not prefix:
required="not first_name|last_name"
cart_empty_redirect
If the cart is empty when the tag renders, the customer is redirected to this path.
cart_empty_redirect="cart/index"
gateway
Sets the payment gateway short name for this checkout form.
- If gateway selection is disabled in CartThrob settings, this parameter is ignored.
You can also post
gatewayfrom an input field (for example, fromgateway_select).gateway=”authorize_net”
gateway_method
Sets an optional gateway-specific method (for gateways that support it).
gateway_method="ideal"
allow_user_price
Allows a posted price form field to set the final payable checkout
total.
This is not a subtotal replacement. CartThrob still records the calculated subtotal, discount, shipping, and tax components, but the confirmed order total and gateway charge use the permitted user value. See Checkout amounts and saved orders.
allow_user_price="yes"
price
Sets a fixed subtotal override for this checkout form.
price="100"
This takes precedence over the subtotal calculated from current product prices and cart items. Tax, shipping, and discounts still apply unless separately overridden.
allow_user_shipping
Allows a posted shipping form field to override calculated shipping during checkout.
allow_user_shipping="yes"
The value must be scalar, numeric, and non-negative. This is a trust boundary, not a secure way to preserve a merchant-calculated shipping price: browser fields remain customer-controlled.
shipping
Sets a fixed shipping override for this checkout form.
shipping="15"
An explicit numeric shipping value takes precedence over
no_shipping/shipping_exempt.
tax
Sets a fixed tax override for this checkout form.
tax="8.25"
An explicit numeric tax value takes precedence over
no_tax/tax_exempt.
no_tax / tax_exempt
Removes tax from the checkout calculation. tax_exempt is retained as
a legacy alias. A truthy checkout exemption overrides mapped product
tax settings and the normally calculated order tax. An explicit
numeric tax parameter has higher precedence.
no_tax="yes"
no_shipping / shipping_exempt
Removes shipping from the checkout calculation. shipping_exempt is
retained as a legacy alias. A truthy checkout exemption overrides
mapped product shipping settings and normally calculated order
shipping. An explicit numeric shipping parameter has higher
precedence.
no_shipping="yes"
country_code (legacy note)
country_code is commonly used as an input field value (for example, name="country_code") and saved through customer-info handling.
In current implementation, it is not a dedicated encoded checkout-form parameter like price, shipping, or tax.
expiration_date
When orders are saved as channel entries, sets entry expiration in days from now.
expiration_date="365"
create_user
Enables member creation during checkout.
create_user="yes"
When enabled, include create-member fields in your form (username, email_address, optional screen_name, password, password_confirm).
group_id
Sets the member group for create_user="yes".
- Values below
5are forced to5. role_idis accepted as an alias in current code.group_id=”6”
force_processing
Skips immediate card charge and marks order as processing.
force_processing="yes"
force_vault
Forces vault creation even when no subscription is present.
force_vault="yes"
vault_id
Uses an existing vault record for payment.
- Vault ownership is validated against the current member.
When
vault_idis used, render{vault_fields}instead of{gateway_fields}.vault_id=”1”
member_id
Admin checkout parameter: checkout on behalf of another member (allowed admin groups only).
member_id="123"
order_id
Completes an existing order in processing status. This parameter is the
order entry ID (ExpressionEngine entry_id), not the invoice ID
contained in the title. If this is set, member_id is ignored.
authorized_redirect, failed_redirect, declined_redirect, processing_redirect
Optional status-specific return paths used after checkout completion.
authorized_redirect="store/order/success"
declined_redirect="store/order/declined"
failed_redirect="store/order/failed"
processing_redirect="store/order/processing"
The Processing destination can receive an offline order awaiting payment or,
with the CartThrob 10 payment-reconciliation update,
a payment requiring merchant review. On the result page, check
{if payment_review} before generic Processing/success wording and display the
message/reference. See
submitted_order_info.
custom_js
Set to yes/true to disable gateway-provided auto-injected JavaScript (form_extra) so you can provide your own implementation.
custom_js="yes"
That is not a styling switch. Custom code owns validation, quote finalization, changed-total confirmation, remote payment preparation, and final CartThrob submission, in that order. Built-in quotes do not cover this path. See Custom checkout JavaScript, Upgrade custom checkout JavaScript for CartThrob 10, and Built-in checkout vs custom JavaScript.
price_change_handling
Controls what the customer sees when CartThrob finalizes checkout and the authoritative total differs from the preview rendered with the form. Valid values are:
none— the default when the parameter is omitted. CartThrob dispatches the price-change events and then continues with the finalized amount without requiring purchaser confirmation.confirm— displays a browser confirmation with the previous total, updated total, changed components, and current subtotal/discount/shipping/tax breakdown.review— displays an accessible same-page review panel and keeps supported hosted payment fields mounted. This is the recommended built-in presentation for most stores.inline— returns to the checkout form and provides the inline price-change variables documented below. The template must display the notice and the customer submits again after reviewing it.template— loads the marked content from the internal EE template configured withprice_change_templateinto a same-page modal. The checkout and supported hosted payment fields remain mounted.
Only a changed final total interrupts checkout. A redistribution among subtotal, discount, shipping, and tax that leaves the total unchanged is informational and does not require confirmation.
price_change_handling is independent of error_handling. For
example, error_handling="inline" without an explicit
price_change_handling still uses the silent none default.
price_change_handling="review"
price_change_template
Required when price_change_handling="template". Use an internal
ExpressionEngine template name in group/template form:
price_change_handling="template"
price_change_template="store/checkout_price_changed"
The target template reads CartThrob’s opaque session-bound state with
{exp:cartthrob:checkout_price_change}.
Wrap the modal content in
data-ct-checkout-price-change-modal, and mark one button with
data-ct-checkout-price-change-confirm and another with
data-ct-checkout-price-change-cancel. CartThrob loads that marked
content over the existing checkout and owns the confirm/cancel
lifecycle. See the
checkout_price_change example.
For compatibility, a target template without the modal marker keeps the older full-page transition. Hosted gateway fields such as Stripe Elements and PayPal Hosted Fields cannot persist their entered values through that navigation, so the customer must enter payment details again after returning. The same warning applies to any custom flow that navigates away from or re-renders the checkout page.
AJAX can also apply coupons or save address/shipping changes and refresh the visible total before the final checkout submission.
Variables
In addition to the variables below, you can use Global Tag Variables, including cart/customer/custom-data values and form error values when inline error handling is used.
- gateway_fields
- vault_fields
- captcha
- checkout amount variables
- inline price-change variables
- checkout action URLs
- errors / field_errors / global_errors
{gateway_fields}
Outputs the HTML fields required by the active gateway.
If gateway selection is enabled, this reflects the selected gateway context.
Gateway-generated fields and inline errors
{gateway_fields} can output standard customer fields as well as the
gateway’s payment UI. In an ordinary checkout, treat that output as the
authoritative set of customer and payment fields. Do not also add handwritten
inputs with the same name or id; duplicate fields can make validation
messages appear beside one input while a different value is submitted.
error_handling="inline" works with gateway-generated fields. Inline handling
means CartThrob renders the same checkout template again and exposes the
form error variables. It does not automatically
insert each error beside the matching generated input. A checkout template
can display:
- an error summary with
{errors}...{/errors}; - a specific message such as
{error:first_name}; - custom gateway-field markup that places errors beside its inputs; or
- JavaScript that maps each
{field}value to the generated field’s ID or name.
Some gateways have an Enable Fields setting. If customer fields are disabled there, add any fields required by your checkout exactly once in the checkout template, or remove validation rules for fields the customer cannot edit. A configured Gateway Fields Template is also a full replacement for the default gateway output and must include every customer field and gateway integration element the checkout needs.
{vault_fields}
When vault_id is used, this outputs gateway vault fields for tokenized payment flows.
{captcha}
When guest checkout CAPTCHA is enabled, outputs ExpressionEngine’s local image CAPTCHA for logged-out customers.
Before enabling CartThrob’s setting:
- turn off EE reCAPTCHA v3, which is not supported by CartThrob’s current word-and-IP validator;
- configure EE’s CAPTCHA filesystem path and public URL;
- make sure the configured directory exists and is writable by PHP; and
- enable the PHP GD extension.
EE’s global Require CAPTCHA? setting may remain off. CartThrob independently decides when guest checkout requires the challenge.
CartThrob validates these prerequisites when its CAPTCHA setting is enabled. If an already-enabled site later loses the image backend, guest checkout fails closed: the form still requires a valid local CAPTCHA answer even if EE cannot create visible image markup.
The setting does not inject markup into an existing custom checkout
template. Every checkout template must include the
{if captcha} block, or logged-out customers will have
no challenge available to complete.
Checkout Amount Variables
The tag calculates a checkout preview from the current cart and supported checkout-form overrides. Render this breakdown next to the payment controls:
{checkout_subtotal}/{checkout_subtotal_numeric}{checkout_discount}/{checkout_discount_numeric}{checkout_shipping}/{checkout_shipping_numeric}{checkout_tax}/{checkout_tax_numeric}{checkout_total_adjustment}/{checkout_total_adjustment_numeric}{checkout_total}/{checkout_total_numeric}{checkout_item_tax}/{checkout_item_tax_numeric}{checkout_shipping_tax}/{checkout_shipping_tax_numeric}{checkout_discount_tax}/{checkout_discount_tax_numeric}{checkout_discount_total}/{checkout_discount_total_numeric}{checkout_has_amount_overrides}{checkout_total_differs_from_cart}{checkout_has_tax_breakdown}and{checkout_tax_allocation}{checkout_subtotal_source},{checkout_shipping_source},{checkout_tax_source}, and{checkout_total_source}{checkout_currency}and{checkout_precision}
The formatted variables are appropriate for customer-facing output. The numeric variants are unformatted decimal values.
The six primary amounts reconcile at the configured currency precision:
checkout_total = checkout_subtotal - checkout_discount + checkout_shipping + checkout_tax + checkout_total_adjustment
checkout_total_adjustment is a signed, display-only reconciliation amount.
It is normally zero and does not add another charge. It can be nonzero when an
allowed customer-entered total or an extension supplies a final total that the
other components cannot describe.
checkout_item_tax is net item tax after the tax reduction represented by
checkout_discount_tax; it excludes shipping tax. checkout_discount_total
is the pre-tax discount plus that tax reduction. Test
checkout_has_tax_breakdown before displaying these details. They are blank,
rather than estimated, when CartThrob cannot allocate the aggregate tax
exactly. checkout_tax remains available, and checkout_tax_allocation is
exact or unallocated.
The source variables explain why an amount has its current value:
calculated: current cart/store calculation;fixed: aprice=,shipping=, ortax=parameter on this form;submitted: a scalar price or shipping value permitted byallow_user_price="yes"orallow_user_shipping="yes"; orextension: the checkout-options hook changed that component.
Checkout parameters configure this rendered form. They do not rewrite the cart
or become state on cart_info. An allowed submitted price or shipping amount
belongs to that submission and finalized quote; a fresh GET returns to the
saved cart plus the parameters declared on the newly rendered form.
The preview is not authoritative. A later address, shipping option, coupon,
permitted customer amount, or checkout-options hook can change the finalized
quote. The same breakdown is available to custom JavaScript through the quote
response and the ct.checkout.quote-finalized and
ct.checkout.amount-changed browser events. See
Custom checkout JavaScript.
Inline Price-Change Variables
These variables are populated after a changed total returns to a form
configured with price_change_handling="inline":
{checkout_price_changed}(1when a notice is present){checkout_price_change_handling}(inline){checkout_changed_components}(pipe-delimited component names){checkout_previous_subtotal},{checkout_previous_discount},{checkout_previous_shipping},{checkout_previous_tax},{checkout_previous_total_adjustment}, and{checkout_previous_total}{checkout_current_subtotal},{checkout_current_discount},{checkout_current_shipping},{checkout_current_tax},{checkout_current_total_adjustment}, and{checkout_current_total}- matched previous/current item-tax, shipping-tax, discount-tax, and discount-total formatted/numeric variables
- previous/current
{checkout_*_has_tax_breakdown}values, allocation values, and amount-source variables
A changed total is not a field-validation failure. Render validation
variables such as {errors}, {field_errors}, {global_errors}, and
{error:field} independently from this notice.
Checkout Action URLs
{checkout_quote_url} and {checkout_confirm_url} are available inside
the tag pair for custom payment JavaScript. Standard gateway JavaScript
uses these automatically. Most templates should not post to them
directly. See
Custom checkout JavaScript.
Error Variables
When using inline error handling, these are available inside the form:
errors_existfield_errors_existglobal_errors_exist{errors}...{/errors}{field_errors}...{/field_errors}{global_errors}...{/global_errors}error:field_name(per-field message)
Conditionals
{if captcha}{if errors_exist}{if checkout_has_amount_overrides}{if checkout_total_differs_from_cart}{if no_results}legacy note
{if captcha}
True when captcha is required for this checkout context.
{if captcha}
<p>Please enter the word in the image:</p>
<p>{captcha}</p>
<input type="text" name="captcha" autocomplete="off" />
{/if}
For Twig/Coilpack, use captcha_required and output the trusted image
markup with the raw filter:
{% if checkout_form.captcha_required %}
<p>Please enter the word in the image:</p>
<p>{{ checkout_form.captcha | raw }}</p>
<input type="text" name="captcha" autocomplete="off">
{% endif %}
The current CartThrob sample checkout templates include this conditional. Existing site templates are not rewritten during an update and must be checked manually.
{if errors_exist}
True when the previous submit failed validation/processing and errors are available.
{if errors_exist}
{errors}
<p>{if field_error}<strong>{field}:</strong>{/if} {error}</p>
{/errors}
{/if}
{if checkout_has_amount_overrides}
True when the checkout tag contains a recognized amount-changing parameter.
{if checkout_total_differs_from_cart}
True when the checkout-aware preview total differs from the current cart total.
{if no_results} (legacy compatibility)
Legacy docs referenced this conditional, but checkout form behavior is currently controlled by cart_empty_redirect or your own outer cart checks (for example with total_items_count).
Use cart_empty_redirect when you want an automatic redirect for empty carts.
Form Fields
- use_billing_info
- price
- shipping
- tax
- coupon_code
- shipping_option / shipping[key]
- custom_data[key]
- meta:some_key
use_billing_info
If truthy, posted shipping fields are copied from billing fields during customer-info save.
<input type="checkbox" name="use_billing_info" value="1" />
price (form field)
Posted price is used only when allow_user_price is enabled.
<input type="text" name="price" value="11" />
shipping (form field)
Posted shipping is used only when allow_user_shipping is enabled.
<input type="hidden" name="shipping" value="10" />
tax (form field)
Posts a tax override value for this checkout request.
<input type="hidden" name="tax" value="2.50" />
coupon_code
Adds a coupon code during checkout submission.
<input type="text" name="coupon_code" />
shipping_option and shipping[key]
These are saved as shipping info during checkout save flow.
<input type="hidden" name="shipping_option" value="ground" />
<input type="hidden" name="shipping[service_code]" value="ground" />
custom_data[key]
Stores custom cart data values on submit.
<input type="hidden" name="custom_data[source]" value="newsletter" />
meta:some_key
For Stripe gateways, posted fields prefixed with meta: are forwarded as Stripe metadata.
<input type="hidden" name="meta:ReferredBy" value="Wanda Sellalot" />
<input type="text" name="meta:GiftFor" placeholder="Gift Recipient Name" />
Behavior and Constraints
- Checkout form validates inventory before rendering. If inventory is invalid, the tag errors immediately.
- If
cart_empty_redirectis provided and cart is empty, customer is redirected. - Gateway selection logic:
- uses configured default gateway by default
- respects
gatewayonly when gateway selection is allowed - can use customer-selected posted gateway
save_customer_info()runs during checkout action unless bypassed by admin/order-specific flows.- Required fields come from gateway-required fields plus your
required/rules:*settings. coupon_codeis applied before payment processing.- Redirect behavior:
returnis standard fallback- status redirects (
authorized_redirect,processing_redirect,declined_redirect,failed_redirect) are supported.
vault_idis ownership-checked; invalid vault IDs are ignored.custom_js="yes"disables auto gateway JS injection so custom integration can control gateway scripts.- Subscription-compatible
subscription_*encoded parameters are accepted by checkout form/action and are processed during checkout subscription handling. - When built-in payment JavaScript is used, omit
idor setid="checkout_form". CartThrob supplies that ID by default. Use a class for styling instead of replacing this functional ID. - Explicit numeric
taxandshippingvalues take precedence over the corresponding exemption flags. - Current mapped product data is evaluated during finalization. Recognized checkout amount/exemption parameters take precedence over the corresponding calculated product, cart, and store values. Inventory validation remains authoritative.
- Non-subscription Stripe Payments, PayPal Standard, and PayPal Advanced use the finalized-quote flow. Do not assume the same amount-changing behavior for subscription checkout or custom payment JavaScript.
- An expired, superseded, consumed, or state-mismatched quote stops checkout rather than authorizing an obsolete amount.
Checkout and Payment Validation Events
Deferred Stripe Payments can dispatch:
ct.payment-form.validation-error
The event detail can include:
fieldErrorsglobalErrorserrorerrorHandlingsource
A custom listener can call event.preventDefault() and take
responsibility for displaying the error. CartThrob field validation,
gateway-element validation, quote-integrity errors, and remote payment
errors are separate layers; not every payment failure belongs beside
one form field.
Checkout Amounts and Saved Orders
CartThrob distinguishes four related stages:
- Cart total — the cart’s current products, discounts, shipping,
and tax. Existing
cart_*variables report this state. - Checkout preview — the current cart with known amount-changing
checkout_formparameters applied. Thecheckout_*variables report this state. - Finalized checkout quote — the server-authoritative calculation after current product settings, submitted customer information, coupon, shipping selection, checkout overrides, and permitted user values have been processed.
- Gateway charge — the amount sent to the payment gateway. In supported built-in gateway flows this must match the confirmed finalized quote.
The saved order’s order-level subtotal, discount, shipping, tax, and total reflect the finalized checkout calculation. Purchased-item rows continue to record the underlying cart item prices.
As a result, a fixed price or a permitted user final total can make
the order total intentionally differ from the sum of ordinary product
prices. The gateway is not charged the old cart total: it receives the
confirmed finalized order total.
For normal stores, prefer this sequence:
- collect and save billing and shipping information;
- select and save the shipping method;
- apply coupons;
- recalculate and display the cart;
- show the customer a final review;
- collect or confirm payment.
Location-sensitive shipping and tax can change when checkout receives a new address. A total displayed before that address reaches the server is an estimate, even when it accurately reflects the current cart.
Reviewing Dynamic Amount Changes
For built-in Authorize.net, PayPal Advanced, PayPal Standard, and
non-subscription Stripe Payments flows, CartThrob finalizes a server
quote before preparing or completing payment. If the finalized total
differs from the amount rendered by checkout_form, CartThrob
dispatches the price-change events before continuing with the finalized
amount.
If price_change_handling is omitted, the none default does not
interrupt the purchaser. Use confirm for the browser confirmation,
review for the built-in same-page panel, inline for template
variables on a re-rendered form, or template for a modal whose content
comes from a dedicated internal template.
Two browser events support custom review interfaces:
ct.checkout.quote-finalizedfires whenever a quote is finalized. Itsdetailincludesquote,previous,current,formattedPrevious,formattedCurrent,changedComponents,requiresConfirmation, andpriceChangeHandling.ct.checkout.amount-changedfires when the finalized total changed. It is cancelable. Callevent.preventDefault()to replace the configured or silent default behavior, then callevent.detail.confirm()orevent.detail.cancel().
Full event payloads, the tokenizer quote path, and custom_js="yes"
examples are in
Custom checkout JavaScript.
Raw component amounts in these event payloads are integer minor units.
Customer-facing formatted values are also available as
formattedPrevious and formattedCurrent. A custom listener must
eventually confirm or cancel; otherwise checkout remains paused.
A same-page modal is often the smoothest option for hosted gateway
fields: the original page and secure payment frames remain mounted
while the customer reviews the new amount. The built-in template
handling provides this when its target uses the documented modal
markers. By contrast, navigating to another page or re-rendering the
full page requires provider-controlled payment details to be entered
again.
Examples
Standard checkout with gateway fields:
{exp:cartthrob:checkout_form
id="checkout_form"
class="form-horizontal"
return="store/order_info"
}
{gateway_fields}
<input type="submit" value="Complete Checkout" />
{/exp:cartthrob:checkout_form}
Recommended same-page price-change review:
{exp:cartthrob:checkout_form
price_change_handling="review"
return="store/order_info"
}
<dl>
<dt>Subtotal</dt><dd>{checkout_subtotal}</dd>
<dt>Discount</dt><dd>-{checkout_discount}</dd>
<dt>Shipping</dt><dd>{checkout_shipping}</dd>
<dt>Tax</dt><dd>{checkout_tax}</dd>
<dt>Expected total</dt><dd><strong>{checkout_total}</strong></dd>
</dl>
{gateway_fields}
<input type="submit" value="Continue to payment" />
{/exp:cartthrob:checkout_form}
Checkout with inline errors and coupon field:
{exp:cartthrob:checkout_form
error_handling="inline"
required="first_name|last_name|email_address"
return="store/order_info"
}
{if errors_exist}
{errors}
<p>
{if field_error}<a href="#{field}"><strong>{field}:</strong></a>{/if}
{error}
</p>
{/errors}
{/if}
{gateway_fields}
{if error:first_name}
<p class="error"><a href="#first_name">First name:</a> {error:first_name}</p>
{/if}
<input type="text" name="coupon_code" />
<input type="submit" value="Checkout" />
{/exp:cartthrob:checkout_form}
Create member during checkout:
{exp:cartthrob:checkout_form
create_user="yes"
group_id="6"
return="store/order_info"
}
{gateway_fields}
<input type="text" name="username" />
<input type="text" name="email_address" />
<input type="text" name="screen_name" />
<input type="password" name="password" />
<input type="password" name="password_confirm" />
<input type="submit" value="Checkout" />
{/exp:cartthrob:checkout_form}
Vault checkout example:
{exp:cartthrob:checkout_form
vault_id="1"
force_vault="yes"
return="store/order_info"
}
{vault_fields}
<input type="submit" value="Charge Saved Method" />
{/exp:cartthrob:checkout_form}