PHP / Magento Dev Blog

  • Publikacje
  • O autorze
  • Kontakt

Checkout customisation – custom fields, JS mixin, step, save to Order

by Henryk Tews / Tuesday, 12 December 2023 / Published in Magento 2

Customising the Magento 2 checkout is one of the most requested – and most feared – development tasks. The checkout is a JavaScript-heavy single-page application built on Knockout.js and RequireJS. Adding custom fields, new steps, or modifying the order of existing ones requires understanding the JS architecture alongside the PHP backend. I show a complete example: custom delivery note field from frontend to Order.

Checkout architecture overview

Magento 2 checkout has two main steps: Shipping and Payment. The frontend is driven by:

  • JS layout – checkout_index_index.xml defines the component tree
  • Knockout.js components – each step/field is a JS component
  • RequireJS mixins – extend existing components without replacing them
  • PHP quote/order persistence – custom data saved via extension attributes

Step 1 – Add custom field to shipping step (JS mixin)

<!-- view/frontend/requirejs-config.js equivalent in etc/view/frontend/requirejs-config.js -->
// view/frontend/web/js/action/set-shipping-information-mixin.js
// Mixin intercepts the "proceed to payment" action and adds our custom data

define(['mage/utils/wrapper', 'Magento_Checkout/js/model/quote'], function (wrapper, quote) {
    'use strict';

    return function (setShippingInformationAction) {
        return wrapper.wrap(setShippingInformationAction, function (originalAction) {
            var shippingAddress = quote.shippingAddress();

            // Read our custom field value from the form
            var deliveryNote = document.getElementById('delivery_note')?.value || '';

            if (shippingAddress && deliveryNote) {
                shippingAddress.extensionAttributes = shippingAddress.extensionAttributes || {};
                shippingAddress.extensionAttributes.delivery_note = deliveryNote;
            }

            return originalAction();
        });
    };
});
// view/frontend/requirejs-config.js
var config = {
    config: {
        mixins: {
            'Magento_Checkout/js/action/set-shipping-information': {
                'Vendor_Module/js/action/set-shipping-information-mixin': true
            }
        }
    }
};

Step 2 – Add the HTML field to the shipping step

<!-- view/frontend/layout/checkout_index_index.xml -->
<?xml version="1.0"?>
<page xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      xsi:noNamespaceSchemaLocation="urn:magento:framework:View/Layout/etc/page_configuration.xsd">
    <body>
        <referenceBlock name="checkout.root">
            <arguments>
                <argument name="jsLayout" xsi:type="array">
                    <item name="components" xsi:type="array">
                        <item name="checkout" xsi:type="array">
                            <item name="children" xsi:type="array">
                                <item name="steps" xsi:type="array">
                                    <item name="children" xsi:type="array">
                                        <item name="shipping-step" xsi:type="array">
                                            <item name="children" xsi:type="array">
                                                <item name="shippingAddress" xsi:type="array">
                                                    <item name="children" xsi:type="array">
                                                        <item name="before-form" xsi:type="array">
                                                            <item name="children" xsi:type="array">
                                                                <item name="delivery-note" xsi:type="array">
                                                                    <item name="component" xsi:type="string">Magento_Ui/js/form/field</item>
                                                                    <item name="config" xsi:type="array">
                                                                        <item name="customScope" xsi:type="string">shippingAddress</item>
                                                                        <item name="template" xsi:type="string">Vendor_Module/form/field</item>
                                                                        <item name="elementTmpl" xsi:type="string">ui/form/element/textarea</item>
                                                                    </item>
                                                                    <item name="provider" xsi:type="string">checkoutProvider</item>
                                                                    <item name="dataScope" xsi:type="string">shippingAddress.extension_attributes.delivery_note</item>
                                                                    <item name="label" xsi:type="string" translate="true">Delivery note</item>
                                                                    <item name="sortOrder" xsi:type="string">200</item>
                                                                </item>
                                                            </item>
                                                        </item>
                                                    </item>
                                                </item>
                                            </item>
                                        </item>
                                    </item>
                                </item>
                            </item>
                        </item>
                    </item>
                </argument>
            </arguments>
        </referenceBlock>
    </body>
</page>

Step 3 – Save delivery note to Quote and Order (PHP)

<!-- etc/extension_attributes.xml -->
<extension_attributes for="Magento\Quote\Api\Data\AddressInterface">
    <attribute code="delivery_note" type="string"/>
</extension_attributes>

<extension_attributes for="Magento\Sales\Api\Data\OrderInterface">
    <attribute code="delivery_note" type="string"/>
</extension_attributes>
<?php

declare(strict_types=1);

namespace Vendor\Module\Plugin;

// Plugin on ShippingInformationManagement - intercepts the checkout "save shipping" call
class SaveDeliveryNotePlugin
{
    public function __construct(
        private \Magento\Quote\Api\CartRepositoryInterface $quoteRepository
    ) {}

    public function beforeSaveAddressInformation(
        \Magento\Checkout\Model\ShippingInformationManagement $subject,
        int $cartId,
        \Magento\Checkout\Api\Data\ShippingInformationInterface $addressInformation
    ): array {
        $shippingAddress = $addressInformation->getShippingAddress();
        $extensionAttrs  = $shippingAddress->getExtensionAttributes();

        $deliveryNote = $extensionAttrs?->getDeliveryNote() ?? '';

        if (!empty($deliveryNote)) {
            $quote = $this->quoteRepository->getActive($cartId);
            $quote->setData('delivery_note', $deliveryNote);
            $this->quoteRepository->save($quote);
        }

        return [$cartId, $addressInformation];
    }
}

// Observer - copy delivery note from Quote to Order when order is placed
class CopyDeliveryNoteToOrderObserver implements \Magento\Framework\Event\ObserverInterface
{
    public function execute(\Magento\Framework\Event\Observer $observer): void
    {
        $order = $observer->getData('order');
        $quote = $observer->getData('quote');

        $deliveryNote = $quote->getData('delivery_note');

        if (!empty($deliveryNote)) {
            $extensionAttrs = $order->getExtensionAttributes()
                ?? $this->orderExtensionFactory->create();
            $extensionAttrs->setDeliveryNote($deliveryNote);
            $order->setExtensionAttributes($extensionAttrs);
            $order->setData('delivery_note', $deliveryNote); // also as direct field
        }
    }
}

Database schema – store delivery note on order

<?php

namespace Vendor\Module\Setup\Patch\Schema;

use Magento\Framework\DB\Ddl\Table;
use Magento\Framework\Setup\Patch\SchemaPatchInterface;

class AddDeliveryNoteToOrder implements SchemaPatchInterface
{
    public function __construct(
        private \Magento\Framework\Setup\SchemaSetupInterface $schemaSetup
    ) {}

    public function apply(): static
    {
        $this->schemaSetup->startSetup();

        $this->schemaSetup->getConnection()->addColumn(
            $this->schemaSetup->getTable('sales_order'),
            'delivery_note',
            [
                'type'     => Table::TYPE_TEXT,
                'length'   => 1000,
                'nullable' => true,
                'comment'  => 'Customer delivery note',
            ]
        );

        $this->schemaSetup->endSetup();
        return $this;
    }

    public static function getDependencies(): array { return []; }
    public function getAliases(): array { return []; }
}

Summary

Checkout customisation in Magento 2 spans five layers: JS layout XML, JS mixin, RequireJS config, extension attributes XML, and PHP plugins or observers. The pattern is always the same: declare the data in extension_attributes.xml, intercept save on the PHP side via plugin, copy from Quote to Order via observer. The XML layout nesting is verbose but follows a consistent structure once you recognise it.

About Henryk Tews

What you can read next

Strategy pattern in PHP – and how Magento 2 uses it in pricing
Xdebug – configuration, PHPStorm, debugging Magento plugins

© 2026 Created by

TOP
Zarządzaj zgodą
Aby zapewnić jak najlepsze wrażenia, korzystamy z technologii, takich jak pliki cookie, do przechowywania i/lub uzyskiwania dostępu do informacji o urządzeniu. Zgoda na te technologie pozwoli nam przetwarzać dane, takie jak zachowanie podczas przeglądania lub unikalne identyfikatory na tej stronie. Brak wyrażenia zgody lub wycofanie zgody może niekorzystnie wpłynąć na niektóre cechy i funkcje.
Funkcjonalne Always active
Przechowywanie lub dostęp do danych technicznych jest ściśle konieczny do uzasadnionego celu umożliwienia korzystania z konkretnej usługi wyraźnie żądanej przez subskrybenta lub użytkownika, lub wyłącznie w celu przeprowadzenia transmisji komunikatu przez sieć łączności elektronicznej.
Preferencje
Przechowywanie lub dostęp techniczny jest niezbędny do uzasadnionego celu przechowywania preferencji, o które nie prosi subskrybent lub użytkownik.
Statystyka
Przechowywanie techniczne lub dostęp, który jest używany wyłącznie do celów statystycznych. Przechowywanie techniczne lub dostęp, który jest używany wyłącznie do anonimowych celów statystycznych. Bez wezwania do sądu, dobrowolnego podporządkowania się dostawcy usług internetowych lub dodatkowych zapisów od strony trzeciej, informacje przechowywane lub pobierane wyłącznie w tym celu zwykle nie mogą być wykorzystywane do identyfikacji użytkownika.
Marketing
Przechowywanie lub dostęp techniczny jest wymagany do tworzenia profili użytkowników w celu wysyłania reklam lub śledzenia użytkownika na stronie internetowej lub na kilku stronach internetowych w podobnych celach marketingowych.
  • Manage options
  • Manage services
  • Manage {vendor_count} vendors
  • Read more about these purposes
Zobacz preferencje
  • {title}
  • {title}
  • {title}