Adding an EAV-based field

Each address form in the checkout is based upon the EAV entity customer_address. By adding a new EAV attribute to this entity, you can easily add your own form field to the checkout. On this page, you will see the steps involved for this.

Adding the EAV attribute to the database

First of all, you will need to add the EAV attribute to the database. This is usually done through a Patch class, but you can also use a third party extension for this (like the Amasty Customer Attributes) extension. The following shows an example Patch class that creates an example EAV attribute example:

namespace YireoTraining\Example\Setup\Patch\Data;

use Magento\Eav\Setup\EavSetup;
use Magento\Eav\Setup\EavSetupFactory;
use Magento\Framework\Setup\ModuleDataSetupInterface;
use Magento\Framework\Setup\Patch\DataPatchInterface;
use Magento\Eav\Model\Config as EavConfig;
use Magento\Customer\Model\ResourceModel\Attribute as AttributeResourceModel;


class AddExampleFieldToCustomerAddress implements DataPatchInterface
{
    public function __construct(
        private EavSetupFactory $eavSetupFactory,
        private ModuleDataSetupInterface $moduleDataSetup,
        private EavConfig $eavConfig,
        private AttributeResourceModel $attributeResourceModel
    ) {
    }

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

    public function getAliases()
    {
        return [];
    }

    public function apply()
    {
        $attributeCode = 'example';
        $attributeLabel = 'Example';
        $eavSetup = $this->eavSetupFactory->create(['setup' => $this->moduleDataSetup]);

        $eavSetup->addAttribute(
            'customer_address',
            $attributeCode,
            [
                'input' => 'text',
                'is_visible_in_grid' => false,
                'visible' => true,
                'user_defined' => true,
                'is_filterable_in_grid' => false,
                'system' => false,
                'label' => $attributeLabel,
                'position' => 10,
                'type' => 'varchar',
                'is_used_in_grid' => false,
                'required' => false,
            ]
        );

        $attribute = $this->eavConfig->getAttribute('customer_address', $attributeCode);

        return $this;
    }
}

Once this class has been created as part of a new module, running bin/magento setup:upgrade should create this EAV-attribute as a row in the database table eav_attribute (amongst others).

This example can be further extended by adding the attribute to frontend and backend forms. See the Yireo_LokiCheckoutCoc module for a full example.

Adding a quote address column to the database

The above adds an attribute to the customer_address entity. While attributes of this entity are automatically copied into a quote address, it doesn't save the quote address attribute value yet. To save a value to the database, we can either add a new column to the quote_address table and sales_order_address table, or we could save things via an extension attribute.

In this example, we simply extend the tables. This can be done with the following db_schema.xml file:

<?xml version="1.0"?>
<schema xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:Setup/Declaration/Schema/etc/schema.xsd">
    <table name="quote_address" resource="default">
        <column xsi:type="varchar" name="example" nullable="true" length="128" comment="Example"/>
    </table>
    <table name="sales_order_address" resource="default">
        <column xsi:type="varchar" name="example" nullable="true" length="128" comment="Example"/>
    </table>
</schema>

After running bin/magento setup:upgrade, the tables should be altered.

Creating a new block

Next, let's create a new block for this EAV attribute via an XML layout file loki_checkout_block_shipping_address.xml:

<?xml version="1.0"?>
<page xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:module:View/Layout:etc/page_configuration.xsd">
    <body>
        <referenceBlock name="loki-checkout.shipping.address.form">
            <block
                name="loki-checkout-example.shipping.address.example" as="example"
                template="Yireo_LokiFieldComponents::form/field.phtml"/>
        </referenceBlock>
    </body>
</page>

Note that the PHTML template Yireo_LokiFieldComponents::form/field.phtml is reused here. Normally, this is what you want: A lot of changes you might want to make to this field can be made via either the ViewModel or the XML layout.

Creating a component

To transform the block above into a full-blown LokiComponent, we need one more step. We need to add a new component to the etc/loki_components.xml file. The name of that component is identical to the name of the block. The group called shippingAddressFields includes a definition for a Context class, ViewModel class and Repository class which we can reuse here. These classes automatically match the name of the block (in our case example) with the attribute code of your EAV attribute (in our case example).

<?xml version="1.0" encoding="UTF-8" ?>
<components xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:module:Yireo_LokiComponents:etc/loki_components.xsd">
    <component
        name="loki-checkout-example.shipping.address.example"
        group="shippingAddressFields"
    />
</components>

See the etc/loki_components.xml of the core module Yireo_LokiCheckout to see the definition of the group shippingAddressFields.

It might be that the original classes need to be modified. In this case, you would create your own classes, extending upon the original. This is actually done for all of the core component classes: Even though they don't have anything specific, they allow for an easier extensibility.

A more customized version could look the following:

<?xml version="1.0" encoding="UTF-8" ?>
<components xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:module:Yireo_LokiComponents:etc/loki_components.xsd">
    <component
        name="loki-checkout-example.shipping.address.example"
        group="shippingAddressFields"
        viewModel="Yireo\LokiCheckoutExample\Component\Checkout\Address\Example\ExampleViewModel"
        repository="Yireo\LokiCheckoutExample\Component\Checkout\Address\Example\ExampleRepository"
    />
</components>

Enhancing the LokiComponent

You could also add <target/> elements, so that other HTML elements are refreshed when your component is being updated. You could add validators and filters as well.

Last modified: April 1, 2025