Adding Custom Checkout Steps
Step Navigator Configuration
// Custom step component
define([
'jquery',
'ko',
'uiComponent',
'Magento_Checkout/js/model/step-navigator'
], function ($, ko, Component, stepNavigator) {
'use strict';
return Component.extend({
defaults: {
template: 'Vendor_Checkout/custom-step'
},
initialize: function () {
this._super();
stepNavigator.registerStep(
'custom-step',
'custom-step',
'Custom Information',
this.isVisible,
ko.observable(3),
'shipping'
);
return this;
},
isVisible: ko.observable(true),
navigate: function () {
this.isVisible(true);
},
stepCode: 'custom-step',
stepTitle: 'Custom Information'
});
});
Step Registration in Layout
<!-- checkout_index_index.xml -->
<item name="custom-step" xsi:type="array">
<item name="component" xsi:type="string">Vendor_Checkout/js/view/custom-step</item>
<item name="sortOrder" xsi:type="string">2</item>
<item name="children" xsi:type="array">
<item name="custom-fieldset" xsi:type="array">
<item name="component" xsi:type="string">Vendor_Checkout/js/view/custom-fieldset</item>
</item>
</item>
</item>
Custom Field Validation
Validation Rules
// Custom field with validation
define([
'jquery',
'ko',
'uiComponent',
'mage/validation'
], function ($, ko, Component) {
'use strict';
return Component.extend({
defaults: {
template: 'Vendor_Checkout/custom-field',
validation: {
'required-entry': true,
'min-text-length': 2,
'max-text-length': 255
}
},
initialize: function () {
this._super();
this.value = ko.observable();
return this;
},
validate: function () {
var $el = this.$el;
return $el.valid();
}
});
});
Custom Validation Rule
// Register custom validation rule
require(['jquery'], function ($) {
$.validator.addMethod(
'validate-phone',
function (value) {
return /^\+?[0-9]{10,15}$/.test(value);
},
'Please enter a valid phone number'
);
});
Server-Side Validation
namespace Vendor\Checkout\Model\Checkout\ConfigProvider;
class ValidationConfigProvider implements \Magento\Checkout\Model\ConfigProviderInterface
{
public function getConfig(): array
{
return [
'validation' => [
'custom_field' => [
'required' => true,
'min_length' => 2,
'max_length' => 255,
'pattern' => '^[a-zA-Z]+$'
]
]
];
}
}
One-Step Checkout Patterns
Single-Page Checkout
// OneStepCheckout component
define([
'jquery',
'ko',
'uiComponent',
'Magento_Checkout/js/model/quote',
'Magento_Checkout/js/action/set-shipping-information',
'Magento_Checkout/js/action/set-payment-information'
], function ($, ko, Component, quote, setShippingAction, setPaymentAction) {
'use strict';
return Component.extend({
defaults: {
template: 'Vendor_Checkout/one-step-checkout'
},
initialize: function () {
this._super();
this.shippingAddress = ko.observable();
this.shippingMethod = ko.observable();
this.paymentMethod = ko.observable();
this.isProcessing = ko.observable(false);
return this;
},
placeOrder: function () {
var self = this;
self.isProcessing(true);
setShippingAction().done(function () {
setPaymentAction().done(function () {
// Place order
self.processOrder();
});
});
},
processOrder: function () {
var self = this;
$.ajax({
url: '/rest/V1/carts/mine/payment-information',
method: 'POST',
data: JSON.stringify({
paymentMethod: { method: self.paymentMethod() },
billingAddress: self.getBillingAddress()
}),
success: function (orderId) {
window.location = '/checkout/onepage/success/?order_id=' + orderId;
}
});
}
});
});
Combined Layout XML
<!-- One-step checkout layout -->
<item name="onestep" xsi:type="array">
<item name="component" xsi:type="string">Vendor_Checkout/js/view/onestep</item>
<item name="sortOrder" xsi:type="string">1</item>
<item name="children" xsi:type="array">
<item name="address" xsi:type="array">
<item name="component" xsi:type="string">Vendor_Checkout/js/view/address</item>
</item>
<item name="shipping-method" xsi:type="array">
<item name="component" xsi:type="string">Vendor_Checkout/js/view/shipping-method</item>
</item>
<item name="payment-method" xsi:type="array">
<item name="component" xsi:type="string">Vendor_Checkout/js/view/payment-method</item>
</item>
</item>
</item>
Complete Custom Checkout Module
Module Structure
Vendor/CustomCheckout/
├── etc/
│ ├── module.xml
│ ├── di.xml
│ └── frontend/
│ └── routes.xml
├── view/
│ └── frontend/
│ ├── layout/
│ │ └── checkout_index_index.xml
│ ├── web/
│ │ ├── js/
│ │ │ └── view/
│ │ │ ├── custom-step.js
│ │ │ └── custom-fieldset.js
│ │ └── template/
│ │ └── custom-step.html
│ └── templates/
└── registration.php
Complete Module Example
// registration.php
<?php
\Magento\Framework\Component\ComponentRegistrar::register(
\Magento\Framework\Component\ComponentRegistrar::MODULE,
'Vendor_CustomCheckout',
__DIR__
);
<!-- etc/module.xml -->
<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="urn:magento:framework:Module:/etc/module.xsd">
<module name="Vendor_CustomCheckout" setup_version="1.0.0">
<sequence>
<module name="Magento_Checkout"/>
</sequence>
</module>
</config>
Quiz
1. How do you register a custom checkout step?
2. What does one-step checkout combine?
3. How to add custom validation to checkout fields?
Flashcards
Question
How to add a checkout step?
Click to reveal answer
Answer
stepNavigator.registerStep() + JS component + layout XML
Question
One-step checkout benefit?
Click to reveal answer
Answer
Reduces checkout friction by showing all fields on one page
Question
Custom validation method?
Click to reveal answer
Answer
$.validator.addMethod('rule-name', validationFn, message)
Question
Step navigation flow?
Click to reveal answer
Answer
shipping → custom-step → review (order defined by sortOrder)
Revision Notes
Key Takeaways
- 1. Custom steps are registered via stepNavigator.registerStep()
- 2. One-step checkout combines address, shipping, and payment on one page
- 3. Custom validation uses jQuery validation or custom rules
- 4. Layout XML defines the component hierarchy and sort order
- 5. Module structure follows standard Magento 2 conventions
Interview Tips
- • Describe the step registration process and sort order impact
- • Discuss pros and cons of one-step vs multi-step checkout
- • Explain how to validate custom fields both client and server side
Cheat Sheet
Custom Checkout:
Register: stepNavigator.registerStep(code, title, sortOrder)
Component: extend uiComponent with template
Layout: add children under checkout > steps
One-Step Checkout:
Combine address + shipping + payment
Single placeOrder() action
Set shipping + payment before order
Validation:
Client: $.validator.addMethod()
Server: ConfigProvider + custom rules