Starting with PrestaShop 9.2, the one-page checkout experience is delivered by the native ps_onepagecheckout module. If you maintain a theme, whether it is a fork of Classic, Hummingbird, or a fully custom theme, this page explains everything you need to know to keep your theme compatible.
ps_onepagecheckout when merchants enable it.ps_onepagecheckout repository is the source of truth, and the relevant files are linked from each section below. Where this page and the module’s code differ, the code wins.ps_onepagecheckout uses the standard PrestaShop module template system. The module ships its own complete set of front-office templates and takes over checkout rendering by returning its template from the hookDisplayOverrideTemplate hook:
// ps_onepagecheckout.php
public function hookDisplayOverrideTemplate(array $params)
{
if (
$this->isOnePageCheckoutEnabled()
&& $params['template_file'] === 'checkout/checkout'
&& $params['controller'] instanceof OrderController
) {
return 'module:ps_onepagecheckout/views/templates/front/checkout/checkout.tpl';
}
return null;
}
This means the checkout works out of the box on any theme that was created from Hummingbird or follows its template structure: you are not required to add any templates for OPC to function. The module’s job is to:
checkout/checkout template when OPC is enabled.one-page-checkout.css).Your theme’s job is to optionally override these templates and styles to match your design, and, when you do, to preserve the DOM contract (IDs, classes, <template> elements) and Smarty variables the module’s JavaScript depends on.
The module ships these templates under views/templates/front/checkout/. To customize the markup, copy a template into your theme at the corresponding override path and edit it there. If your override drops a required ID, class, or <template> element, the checkout JavaScript will break, so treat the selectors in the Required DOM structure section as a contract.
Module: views/templates/front/checkout/_partials/steps/one-page-checkout.tpl
Theme override: modules/ps_onepagecheckout/views/templates/front/checkout/_partials/steps/one-page-checkout.tpl
This is the primary template, rendered instead of the native multi-step checkout when OPC is active. It receives all variables listed in the Smarty variables section below. It assembles the checkout by {include}-ing the partials below.
The full set of partials shipped under _partials/one-page-checkout/ is available to preview in the module’s repository.
Some of these are re-rendered server-side and injected via AJAX as the customer interacts with the checkout. Each is produced by a dedicated front controller:
| Template | Rendered by controller | When |
|---|---|---|
addresses-section.tpl |
addressform |
Address form display (new address, address editing) |
address-list.tpl |
addresseslist |
List of saved addresses after an update |
carriers.tpl |
carriers |
Carrier list refresh after address change |
payment-methods.tpl |
paymentmethods |
Payment method list refresh |
The remaining partials (contact-section.tpl, delivery-section.tpl, payment-section.tpl, order-options.tpl, address-fields.tpl, and others) are rendered as part of the initial page and included by the templates above.
The module assigns is_one_page_checkout_enabled to the Smarty context on the checkout page (it is set from hookActionFrontControllerSetVariables, which only runs when the current controller is the OrderController). Use it to conditionally render OPC markup:
{if $is_one_page_checkout_enabled}
{* OPC-specific markup *}
{else}
{* Native multi-step checkout *}
{/if}
This variable is true only when:
ps_onepagecheckout is the active checkout provider.{if isset($is_one_page_checkout_enabled) && $is_one_page_checkout_enabled} if you reference it outside the checkout.The following variables are available in one-page-checkout.tpl. They are assigned by CheckoutOnePageStep (and OnePageCheckoutForm::getTemplateVariables()) during checkout rendering. This list covers the variables you are most likely to use: the shipped checkout templates are the authoritative reference for everything available in context.
{$customer.id} {* int *}
{$customer.firstname} {* string *}
{$customer.lastname} {* string *}
{$customer.email} {* string *}
{$customer.is_guest} {* bool *}
{$customer.is_logged} {* bool *}
{$customer.addresses} {* array of saved address objects *}
Each address in $customer.addresses:
{$address.id} {* int (normalized from id_address) *}
{$address.id_address} {* int *}
{$address.alias} {* string *}
{$address.firstname} {* string *}
{$address.lastname} {* string *}
{* ... all standard Address fields *}
{$action} {* string: form action URL *}
{$token} {* string: CSRF token *}
{$formFields} {* all form fields as FormField objects *}
{$contactFields} {* email / contact info fields *}
{$additionalCustomerFields} {* extra customer personal info (from modules) *}
{$useSameAddressField} {* the "use same address for billing" checkbox field *}
{$deliveryFields} {* delivery address form fields *}
{$invoiceFields} {* billing address form fields *}
{$invoiceMetaFields} {* billing address metadata fields *}
Each FormField object exposes:
name: HTML name attributetype: input type (text, email, select, radio, and so on)value: current valuelabel: user-facing labelrequired: boolerrors: array of validation error stringsavailableValues: options for select and radio types{$delivery_options} {* array: available carriers *}
{$delivery_option} {* string: selected carrier key *}
{$selected_delivery_option} {* array: details of selected carrier *}
{$is_virtual_cart} {* bool: true for digital-only carts *}
{$delivery_message} {* string: carrier messaging *}
{$recyclable} {* bool *}
{$recyclablePackAllowed} {* bool *}
{$gift.allowed} {* bool *}
{$gift.isGift} {* bool *}
{$gift.message} {* string *}
{$payment_options} {* array: available payment modules *}
{$selected_payment_module} {* string: selected payment module name *}
{$selected_payment_selection_key}{* string: selected payment option key *}
{$is_free} {* bool: true when cart total is 0 *}
{$conditions_to_approve} {* associative array: condition key => pre-rendered HTML label *}
conditions_to_approve comes from PrestaShop core (ConditionsToApproveFinder). It is an associative array keyed by the condition identifier, where each value is the already-rendered HTML for the condition’s label. The shipped template iterates it like this:
{foreach from=$conditions_to_approve item="condition" key="condition_name"}
<input type="checkbox" name="conditions_to_approve[{$condition_name}]" value="1" required>
<label>{$condition nofilter}</label>
{/foreach}
There is no per-condition id_module, id_condition, or required object exposed by the module: the value is the rendered label string.
{$errors} {* array: general errors *}
{$validation_errors} {* associative array: field name to error array *}
{$validation_error_messages} {* array<string>: flattened error messages *}
{$opc_urls} {* array: same controller URLs exposed in window.ps_onepagecheckout.urls *}
These contain pre-rendered HTML from other modules:
{$hookDisplayBeforeCarrier} {* HTML: render before carrier list *}
{$hookDisplayAfterCarrier} {* HTML: render after carrier list *}
Always output these unescaped:
{$hookDisplayBeforeCarrier nofilter}
{$hookDisplayAfterCarrier nofilter}
The module’s JavaScript finds elements and injects content through a fixed set of IDs and CSS classes. If you override a template and drop one of these, that part of the checkout stops working. The complete, authoritative mapping is in views/js/selectors.js: treat it as the contract and keep these anchors in your overrides.
The load-bearing ones are:
.one-page-checkout: the main wrapper. Its presence activates all OPC JavaScript.#opc-form: the primary <form> that wraps every checkout field.#opc-delivery-address-content-list and #opc-billing-address-content-list (saved addresses), #opc-delivery-methods (carriers), and #opc-payment-methods (payment methods).#opc-pay-button and #opc-pay-amount: the submit button (disabled automatically while the form is invalid) and the total-to-pay text node.#pay-with-{paymentOptionId}-form for each payment option.input[name^="conditions_to_approve["][required] gate the pay button.Two structural details are easy to get wrong when overriding:
<template> elements (rendered inside delivery-section.tpl and payment-section.tpl): #opc-template-loader, #opc-template-carriers-error, and #opc-template-payment-error. There is no #opc-template-payment-loader: the payment list reuses the generic loader. Keep all three in your overrides.addresses-section.tpl: #modal-delivery and #modal-invoice (their content is injected server-side by the addressform controller). The JavaScript also accepts a legacy #opc-address-modal id, but the shipped templates emit only the two above, so keep at least those.The module communicates checkout state changes through the PrestaShop event bus. Listen with prestashop.on() to re-initialize widgets after an AJAX refresh or to hook into the checkout lifecycle:
// Re-mount widgets when a section is re-rendered over AJAX.
prestashop.on('opcCarriersUpdated', () => remountMyCarrierWidgets());
prestashop.on('opcPaymentMethodsUpdated', () => remountMyPaymentWidgets());
// React just before the final submission (no payload).
// This is a prestashop event, not a DOM event: use prestashop.on(), not addEventListener().
prestashop.on('opcFinalSubmitStarted', () => { /* ... */ });
The events fall into a few families:
opcCarriersLoading, opcCarriersUpdated, opcCarrierSelected, opcCarriersFailed, opcCarriersRetry.opcPaymentMethodsLoading, opcPaymentMethodsUpdated, opcPaymentMethodSelected, opcPaymentMethodsFailed, opcPaymentMethodsRetry, opcPaymentMethodsRefreshed.opcDeliveryAddressSelected, opcBillingAddressSelected, opcDeliveryAddressUpdated, opcBillingAddressUpdated, opcBillingSectionToggled, updatedOpcAddressForm.opcGuestInitSuccess, opcGuestInitFailed.opcFinalSubmitStarted, opcFormValidated, opcSubmitFailed.opcCartSummaryBeforeUpdate, opcCartSummaryUpdated.views/js/events.js (OPC_EVENTS). Use it as the authoritative reference: event payloads can change as the module evolves.When OPC is enabled, the module injects a window.ps_onepagecheckout object into the checkout page (via Media::addJsDef from ps_onepagecheckout.php). It exposes:
enabled: whether OPC is active.urls: a map of every AJAX endpoint the front-end calls, keyed by name (for example urls.carriers, urls.paymentMethods, urls.opcSubmit). The set of endpoints grows with the module, so read keys from this object rather than assuming a fixed list.messages: localized error strings used by the JavaScript for toasts.The same URL map is also exposed to Smarty as {$opc_urls}.
window.ps_onepagecheckout.urls (or {$opc_urls} in templates) so your code stays correct across shops with custom URL rewrites and across module versions that add or rename endpoints.The module registers its front-office bundles on hookActionFrontControllerSetMedia with explicit priorities (currently in the 149-158 range). If your theme loads JavaScript that depends on OPC events, register it with a priority higher than the highest OPC bundle so it runs after all of them, then bind your listeners. The exact priorities are set in ps_onepagecheckout.php; do not assume they are stable across versions.
views/public/ is loaded on the checkout page. In particular, opc-checkout-layout.bundle.js is used by the back-office configuration screen. Rely on the OPC events and the runtime object rather than on a specific bundle being present.The module registers one baseline front-office stylesheet, views/public/one-page-checkout.css (handle module-ps-onepagecheckout, priority 200, media all), to give the checkout a usable default appearance. It is intentionally minimal: most of the visual styling is still your theme’s responsibility.
To restyle the checkout:
one-page-checkout.css from your theme, or register your own stylesheet at a higher priority.The module also ships views/css/checkout_layout_choice.css, which is used only by the back-office configuration page and is never loaded on the front office.
The module works out of the box with its shipped templates and styles. This checklist matters when your theme overrides the OPC templates. Verify before releasing a theme update for PrestaShop 9.2:
one-page-checkout.tpl, it lives at templates/checkout/_partials/steps/one-page-checkout.tpl.addresses-section, address-list, carriers, payment-methods) keep their AJAX target containers..one-page-checkout wrapper is present in the main template.#opc-form wraps all form fields.<template> loading/error elements are present (#opc-template-loader, #opc-template-carriers-error, #opc-template-payment-error).#modal-delivery, #modal-invoice) are present.$hookDisplayBeforeCarrier and $hookDisplayAfterCarrier are rendered with {nofilter}.is_one_page_checkout_enabled is checked (with isset) before rendering OPC-specific markup.window.ps_onepagecheckout.urls, not hardcoded paths.actionCheckoutBuildProcess hook, and payment and carrier module compatibility.