sbodak / magento2-checkout-custom-form

sbodak/magento2-checkout-custom-form

Add a custom form to Magento 2 checkout on the shipping step.

  • Slawomir Bodak
magento2-module 2.4.6-2.4.9 Compatible Based on composer requirements only QA: failed MIT

Magento 2 – Checkout Custom Form

Overview

Adds a configurable custom form to the Magento 2 checkout (shipping step), placed above the shipping-method selection.
The form works for both logged-in customers and guests.
After an order is placed, all entered data is stored in the sales_order table and exposed via extension attributes on OrderInterface.
Form data persists through page refreshes as long as the cart is active.

Fields can be individually enabled or disabled per store view from the admin panel.
Optional character-length limits can be configured for each field.

Default form fields

Field Config key
Buyer name checkout_buyer_name
Buyer email checkout_buyer_email
Purchase order no. checkout_purchase_order_no
Goods mark checkout_goods_mark
Comment checkout_comment

API endpoints

Method Endpoint Auth
PUT /V1/carts/mine/set-order-custom-fields Customer
PUT /V1/guest-carts/:cartId/set-order-custom-field Guest

Compatibility

Module version Magento PHP
2.0.* 2.4.6 – 2.4.8 8.1 · 8.2 · 8.3 · 8.4
1.2.* 2.3.x 7.x
1.1.* 2.1.x – 2.2.x (no longer supported)

Installation

composer require sbodak/magento2-checkout-custom-form
php bin/magento module:enable Bodak_CheckoutCustomForm
php bin/magento setup:upgrade
php bin/magento setup:di:compile
php bin/magento cache:flush

Admin Configuration

Go to Stores → Configuration → Sales → Checkout → Checkout Custom Form Configuration.

  • Enabled Form Fields – multiselect; choose which fields appear in the checkout.
  • ** Character Limit fields – optionally restrict the maximum length of each field (leave empty for no limit).

Customisation

Adding / removing fields

  1. Add the constant to Api/Data/CustomFieldsInterface.php and the ATTRIBUTES array.
  2. Add the column to Setup/Patch/Data/AddCustomFields.php.
  3. Extend Model/Data/CustomFields.php with getters, setters, and isXxxEnabled().
  4. Register the field in Model/Config/Source/Option.php.
  5. Add it to Model/Checkout/LayoutProcessor/Plugin.php ($fields array).
  6. Update Observer/AddCustomFieldsToOrder.php if custom mapping is required.
  7. Update Observer/Sales/OrderLoadAfter.php to populate the extension attribute.
  8. Update templates in view/adminhtml/templates/order/view/custom_fields.phtml
    and view/frontend/templates/order/view/custom_fields.phtml.

Making a field required

In Model/Checkout/LayoutProcessor/Plugin.php, add a validation key to the field array:

[
    'dataScopeName' => CustomFieldsInterface::CHECKOUT_PURCHASE_ORDER_NO,
    'label'         => 'Purchase order no.',
    'validation'    => ['required-entry' => true],
],

Overriding translations

Edit i18n/en_US.csv (or create a language-specific CSV, e.g. de_DE.csv).


Screenshots

Checkout – Guest

[image: Checkout frontend custom form – Guest]

Checkout – Logged in

[image: Checkout frontend custom form – Logged in]

Customer account – Order history

[image: Customer account – Order history view]

Admin panel – Order edit

[image: Admin panel – order edit]


Running Tests

composer install
vendor/bin/phpunit

Uninstall

php bin/magento module:uninstall Bodak_CheckoutCustomForm

This drops the custom columns from the sales_order and quote tables.


Changelog

See CHANGELOG.md.

License

MIT License

Changelog

All notable changes to this project will be documented in this file.
The format is based on Keep a Changelog,
and this project adheres to Semantic Versioning.

[2.0.0] - 2026-04-18

Breaking Changes

  • Moved all module source code into src/ directory; only composer.json, LICENSE, README.md,
    docs/, and CHANGELOG.md remain in the repository root.
  • Replaced deprecated Setup/InstallData.php with Setup/Patch/Data/AddCustomFields.php
    (Magento 2.3+ declarative data-patch API).
  • Removed setup_version from etc/module.xml (no longer required in Magento 2.4).
  • Setter and getter signatures now use proper ?string nullable types and return types.

Added

  • Magento 2.4.8 compatibility — tested against M2.4.7 / M2.4.8.
  • PHP 8.1 – 8.4 support in composer.json.
  • Admin configuration (etc/adminhtml/system.xml + etc/config.xml): enable/disable
    individual form fields per store view and optionally set per-field character limits.
  • Helper/Config — centralises access to bodak/checkout/* store configuration.
  • Model/Checkout/LayoutProcessor/Plugin — injects enabled fields into the checkout JS layout
    at runtime, replacing the hardcoded checkout_index_index.xml field definitions.
  • Model/Config/Source/Option — multiselect source model for the admin field selector.
  • Model/CustomFields/Validator — validates field lengths against configured limits before
    persisting to the quote; uses Laminas\Filter\Word\UnderscoreToCamelCase.
  • Observer/Sales/OrderLoadAfter — populates Sales Order extension attributes after load
    so custom fields are available via the REST API response.
  • etc/extension_attributes.xml — declares extension attributes on OrderInterface for all
    five custom fields.
  • etc/events.xml — added sales_order_load_after observer.
  • etc/di.xml — added LayoutProcessor plugin registration.
  • i18n/de_DE.csv — German translations.
  • Custom textarea template (view/frontend/web/template/form/element/textarea.html).
  • Unit test suite (tests/Unit/) covering CustomFields, Config, Validator, and
    AddCustomFieldsToOrder.

Changed

  • Observer/AddCustomFieldsToOrder — only copies fields that are enabled in admin config
    (previously always copied all fields).
  • Frontend and admin templates now conditionally render only enabled fields via
    isCheckoutXxxEnabled() methods.
  • checkout_index_index.xml simplified: field definitions are now managed entirely by the
    LayoutProcessor plugin rather than XML items.
  • Dependency declarations in composer.json now include magento/module-checkout,
    magento/module-quote, magento/module-sales, and magento/module-store.
  • Templates updated from deprecated /* @escapeNotVerified */ comments to $block->escapeHtml()
    short syntax (<?= ?>).
  • Module sequence in etc/module.xml extended to include Magento_Checkout, Magento_Quote,
    and Magento_Store.

Fixed

  • Model/Data/CustomFields now correctly calls parent::__construct() on
    AbstractExtensibleObject.

[1.2.1] - Previous release

  • Magento 2.3 compatibility.

[1.1.*] - Legacy

  • Magento 2.1.x – 2.2.x support (no longer maintained).
Versions
Version Stability QA Status Released
2.0.0 stable Fail 2026-04-18 21:55:13
1.2.1 stable Not tested 2019-03-27 21:10:42
1.2.0 stable Not tested 2019-03-14 18:32:37
1.1.4 stable Not tested 2018-09-19 20:57:26
1.1.3 stable Not tested 2018-09-19 20:51:45
1.1.2 stable Not tested 2018-04-04 18:42:51
1.1.1 stable Not tested 2018-03-03 15:14:12
1.1.0 stable Not tested 2018-03-03 13:12:24
1.0.3 stable Not tested 2017-11-13 08:51:33
1.0.2 stable Not tested 2017-11-12 21:21:21
1.0.1 stable Not tested 2017-11-11 18:09:18
1.0.0 stable Not tested 2017-11-11 17:23:55

Requires 7

Package Constraint
laminas/laminas-filter ^2.25
magento/framework ^103.0
magento/module-checkout ^100.4
magento/module-quote ^101.2
magento/module-sales ^103.0
magento/module-store ^101.1
php ^8.1|^8.2|^8.3|^8.4

Requires-dev 1

Package Constraint
phpunit/phpunit ^10.5|^11.0
QA results
Tool Status Findings Summary
PHPCS Fail 2 2 errors (gating threshold: error-severity=10, ruleset: Magento2)
PHPStan Fail 3 3 errors (level 4, ruleset: phpstan + bitexpert/phpstan-magento)
Cpd Pass 0
Security Pass 0
License
MIT
Authors
Make it pay

Turn an existing module into recurring revenue.

If you already maintain a Magento 2 module on GitHub or GitLab, listing it on Packagento takes about five minutes. We mirror your tags, handle distribution signing, and route paid licenses through Stripe Connect, so you can keep shipping the way you already do.