I have created a custom custom_address attribute "a_mobile_phone". I can save the attribute in the admin and the customer address edit form.
I have added this attribute to the checkout address form with no issue but it is not saving in the database.
I have a plugin:
//Vendor/Module/etc/frontend/di.xml <config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd"> <type name="Magento\Checkout\Block\Checkout\LayoutProcessor"> <plugin name="add-mobile-phone-field" type="Vendor\Module\Plugin\LayoutProcessorPlugin" sortOrder="10"/> </type> </config> My file:
//Vendor/Module/Plugin/LayoutProcessorPlugin.php public function afterProcess( \Magento\Checkout\Block\Checkout\LayoutProcessor $subject, array $jsLayout ) { $customAttributeCode = 'a_mobile_phone'; $customField = [ 'component' => 'Magento_Ui/js/form/element/abstract', 'config' => [ // customScope is used to group elements within a single form (e.g. they can be validated separately) 'customScope' => 'shippingAddress.custom_attributes', 'customEntry' => null, 'template' => 'ui/form/field', 'elementTmpl' => 'ui/form/element/input', 'tooltip' => [ 'description' => 'Pour la livraison', ], ], 'dataScope' => 'shippingAddress.custom_attributes' . '.' . $customAttributeCode, 'label' => 'Téléphone portable', 'provider' => 'checkoutProvider', 'sortOrder' => 200, 'validation' => [ 'required-entry' => true ], 'options' => [], 'filterBy' => null, 'customEntry' => null, 'required' => true, '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; } My fieldset:
//Vendor/Module/etc/fieldset.xml <config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:DataObject/etc/fieldset.xsd"> <scope id="global"> <fieldset id="sales_convert_quote_address"> <field name="a_mobile_phone"> <aspect name="to_order_address" /> <aspect name="to_customer_address" /> </field> </fieldset> </scope> </config> The mixin:
//Vendor/Module/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 } } } }; The mixin file:
//Vendor/Module/view/frontend/web/js/action/set-shipping-information-mixin.js 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'] = {}; } shippingAddress['extension_attributes']['a_mobile_phone'] = jQuery('[name="a_mobile_phone"]').val(); return originalAction(); }); }; }); The extension attribute :
//Vendor/Module/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="Magento\Sales\Api\Data\OrderAddressInterface"> <attribute code="a_mobile_phone" type="string" /> </extension_attributes> <extension_attributes for="Magento\Customer\Api\Data\AddressInterfacee"> <attribute code="a_mobile_phone" type="string" /> </extension_attributes> <extension_attributes for="Magento\Quote\Api\Data\AddressInterface"> <attribute code="a_mobile_phone" type="string" /> </extension_attributes> <extension_attributes for="Magento\Checkout\Api\Data\ShippingInformationInterface"> <attribute code="a_mobile_phone" type="string" /> </extension_attributes> </config> With all this my attribute is displayed in the checkout. When I set the value in a new address I have the code displayed by the knockoutJs. 
But my biggest issue is that the attribute value is not save in the DB when I pass the order.
What am I missing to save the attribute value ? I followed the magento doc for this : https://devdocs.magento.com/guides/v2.3/howdoi/checkout/checkout_new_field.html
EDIT 1 :
So I managed to save the data to the address with :
Adding the field to 2 table in the DB :
$installer->getConnection() ->addColumn( $setup->getTable('quote_address'), "a_mobile_phone", ['type' => Table::TYPE_TEXT, 'nullable' => true, 'comment' => 'Phone for delivery'] ); $installer->getConnection() ->addColumn( $setup->getTable('sales_order_address'), "a_mobile_phone", ['type' => Table::TYPE_TEXT, 'nullable' => true, 'comment' => 'Phone for delivery'] ); Adding a new mixin :
'Magento_Checkout/js/model/shipping-save-processor/payload-extender': { 'Vendor_Module/js/model/shipping-save-processor/payload-extender': true } The mixin : Vendor/Module/view/frontnend/web/js/model/shipping-save-processor/payload-extender.js
define([ 'jquery', 'mage/utils/wrapper' ], function ( jQuery, wrapper ) { 'use strict'; return function (processor) { return wrapper.wrap(processor, function (proceed, payload) { payload = proceed(payload); var shippingAddress = payload.addressInformation.shipping_address; var aMobilePhone = jQuery('[name="custom_attributes[a_mobile_phone]"]').val(); if(aMobilePhone == "" || aMobilePhone == null){ if(shippingAddress.customAttributes == "undefined" || shippingAddress.customAttributes == null){ aMobilePhone = null; } else { if(shippingAddress.customAttributes.a_mobile_phone == "undefined" || shippingAddress.customAttributes.a_mobile_phone == null) { aMobilePhone = null; } else { aMobilePhone = shippingAddress.customAttributes.a_mobile_phone.value; } } } var goneExtentionAttributes = { 'aMobilePhone': aMobilePhone }; payload.addressInformation.extension_attributes = _.extend( payload.addressInformation.extension_attributes, goneExtentionAttributes ); return payload; }); }; }); Modify set-shipping-information-mixin.js as suggested by Amit Bera :
$.each(shippingAddress.customAttributes, function(index, eachCustomAttribute){ console.log(eachCustomAttribute); if(eachCustomAttribute.attribute_code == 'a_mobile_phone') { shippingAddress['extension_attributes']['a_mobile_phone'] = eachCustomAttribute.value ; } }); A new plugin on : Magento\Checkout\Model\ShippingInformationManagement
class ShippingAddressManagementPlugin { /** * @var LoggerInterface $logger */ protected $logger; /** * ShippingAddressManagementPlugin constructor. * @param LoggerInterface $logger */ public function __construct( LoggerInterface $logger ) { $this->logger = $logger; } /** * @param ShippingAddressManagement $subject * @param $cartId * @param AddressInterface $address */ public function beforeAssign( ShippingAddressManagement $subject, $cartId, AddressInterface $address ) { $extAttributes = $address->getExtensionAttributes(); if (!empty($extAttributes)) { try { $address->setAMobilePhone($extAttributes->getAMobilePhone()); } catch (\Exception $e) { $this->logger->critical($e->getMessage()); } } } } I have just one issue left, the attribute code "a_mobile_phone" displayed inside the address summary (see under)
EDIT 2
Thanks to @coderGeek i have fixed th last issue :
I have modified the requirejs-config.js file and added the default.html one
//file : Vendor/Module/view/web/template/shipping-address/address-renderer/default.html <div class="shipping-address-item" css="'selected-item' : isSelected() , 'not-selected-item':!isSelected()"> <text args="address().prefix"/> <text args="address().firstname"/> <text args="address().middlename"/> <text args="address().lastname"/> <text args="address().suffix"/><br/> <text args="_.values(address().street).join(', ')"/><br/> <text args="address().city "/>, <span text="address().region"></span> <text args="address().postcode"/><br/> <text args="getCountryName(address().countryId)"/><br/> <a if="address().telephone" attr="'href': 'tel:' + address().telephone" text="address().telephone"></a><br/> <each args="data: address().customAttributes, as: 'element'"> <each args="data: Object.keys(element), as: 'attribute'"> <if args="typeof element[attribute] === 'object'"> <if args="element[attribute].label"> <text args="element[attribute].label"/> </if> <ifnot args="element[attribute].label"> <if args="element[attribute].value"> <text args="element[attribute].value"/> </if> </ifnot> <br/> </if> <if args="typeof element[attribute] === 'string'"> <if args="attribute == 'value'"> <text args="element[attribute]"/> <br/> </if> </if> </each> </each> <button visible="address().isEditable()" type="button" class="action edit-address-link" click="editAddress"> <span translate="'Edit'"></span> </button> <!-- ko if: (!isSelected()) --> <button type="button" click="selectAddress" class="action action-select-shipping-item"> <span translate="'Ship Here'"></span> </button> <!-- /ko --> </div> 
