Skip to content

Extension Attributes not storing data

I am working through the Add a new field in the address form tutorial. I’ve copied the tutorial, and the custom field is appearing on my Checkout page. However the fields are not stored when submitting the checkout. In fact, when dumping the database, I can see that unique strings stored in the Custom Attribute field are not present in the dump. This is on a vanilla Magento 2.4.4 running locally in a Docker container on my Ubuntu desktop.

These are my files, what have I done wrong?

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="Dc_Foo" setup_version="0.0.1">
        <sequence>
            <module name="Magento_Customer" />
            <module name="Magento_Checkout" />
        </sequence>
    </module>
</config>

registration.php

<?php
use MagentoFrameworkComponentComponentRegistrar;
ComponentRegistrar::register(ComponentRegistrar::MODULE, 'Dc_Foo', __DIR__);

Plugin/Block/Checkout/LayoutProcessor.php

<?php
namespace DcFooPluginBlockCheckout;
use MagentoCheckoutBlockCheckoutLayoutProcessor as LayoutProcessor_stock;

class LayoutProcessor {

    public function afterProcess(LayoutProcessor_stock $subject, array $jsLayout)
    {
        $customAttributeCode = 'foo';

        $customField = [
            'component' => 'Magento_Ui/js/form/element/abstract',
            'config' => [
                'customScope' => 'shippingAddress.custom_attributes',
                'customEntry' => null,
                'template' => 'ui/form/field',
                'elementTmpl' => 'ui/form/element/input',
                'tooltip' => [
                    'description' => 'The fine Foo field.',
                ],
            ],
            'dataScope' => 'shippingAddress.custom_attributes' . '.' . $customAttributeCode,
            'label' => 'Foo',
            'provider' => 'checkoutProvider',
            'sortOrder' => 0,
            'validation' => [
                'required-entry' => false
            ],
            'options' => [],
            'filterBy' => null,
            'customEntry' => null,
            'visible' => true,
            'value' => '' // value field is used to set a default value of the attribute
        ];

        $jsLayout['components']['checkout']['children']['steps']['children']['shipping-step']['children']['shippingAddress']['children']['shipping-address-fieldset']['children'][$customAttributeCode] = $customField;

        return $jsLayout;
    }

}

etc/frontend/di.xml

<?xml version="1.0" encoding="UTF-8"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd">

    <type name="MagentoCheckoutBlockCheckoutLayoutProcessor">
        <plugin name="dc-foo-add-foo-to-addresses" type="DcFooPluginBlockCheckoutLayoutProcessor" sortOrder="10" />
    </type>

</config>

view/frontend/web/js/action/set-shipping-information-mixin.js

/*jshint browser:true jquery:true*/
/*global alert*/
define([
    'jquery',
    '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();
            if (shippingAddress['extension_attributes'] === undefined) {
                shippingAddress['extension_attributes'] = {};
            }

            var attribute = shippingAddress.customAttributes.find(
                function (element) {
                    return element.attribute_code === 'foo';
                }
            );

            shippingAddress['extension_attributes']['foo'] = attribute.value;
            // pass execution to original action ('Magento_Checkout/js/action/set-shipping-information')
            return originalAction();
        });
    };
});

view/frontend/requirejs-config.js

var config = {
  config: {
    mixins: {
      'Magento_Checkout/js/action/set-shipping-information': {
        'Dc_Foo/js/action/set-shipping-information-mixin': true
      },
    }
  }
};

etc/extension_attributes.xml

<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:Api/etc/extension_attributes.xsd">
    <extension_attributes for="MagentoQuoteApiDataAddressInterface">
        <attribute code="foo" type="string" />
    </extension_attributes>
</config>

I’ve then run the following CLI commands (output truncated with ... where appropriate):

$ bin/magento cache:clean
Cleaned cache types:
config
layout
block_html
collections
reflection
db_ddl
compiled_config
eav
customer_notification
config_integration
config_integration_api
full_page
config_webservice
translate

$ bin/magento module:enable Dc_Foo"
The following modules have been enabled:
- Dc_Foo

To make sure that the enabled modules are properly registered, run 'setup:upgrade'.
Cache cleared successfully.
Generated classes cleared successfully. Please run the 'setup:di:compile' command to generate classes.
Info: Some modules might require static view files to be cleared. To do this, run 'module:enable' with the --clear-static-content option to clear them.

$ bin/magento setup:upgrade
Cache types config flushed successfully
Cache cleared successfully
File system cleanup:
...
The directory '/var/www/foo/generated/metadata/' doesn't exist - skipping cleanup
...
Updating modules:
Cache cleared successfully
Schema creation/updates:
Module 'Dc_Foo':
...
Module 'Magento_ComposerRootUpdatePlugin':
Running data recurring...Web Setup Wizard installation of "magento/composer-root-update-plugin" failed; unable to load /var/www/html/composer.json.
...
Module 'Dc_Foo':
...
Enabling caches:
Current status:
layout: 1
block_html: 1
full_page: 1
Nothing to import.
Media files stored outside of 'Media Gallery Allowed' folders will not be available to the media gallery.
Please refer to Developer Guide for more details.

$ bin/magento setup:di:compile
Compilation was started.
Plugin list generation... 9/9 [============================] 100% 47 secs 430.0 MiB
Generated code and dependency injection configuration successfully.

Now, I see the field where I expect it on the Shipping Page:

happy-foo-field

And I see that the data was passed to the Review Page:

Field on Review page

And I certainly completed the order:

Field on Thank You page

But nothing in the fine database?!?

$ mysqldump -uroot -p foodb --skip-extended-insert --default-character-set=utf8mb4 -r dump-foo.sql

$ grep "happy-foo-field" dump-foo.sql

$ wc -l dump-foo.sql
25136 dump-foo.sql

$ head dump-foo.sql # The file is valid
-- MySQL dump 10.13  Distrib 8.0.31, for Linux (x86_64)
--
-- Host: localhost    Database: foodb
-- ------------------------------------------------------
-- Server version       8.0.28

/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;
/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */;
/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */;
/*!50503 SET NAMES utf8mb4 */;