How to Add Custom Fields in Magento 2 Checkout?

Checkout is one of the most essential steps your customers take in their journey. Thus, it's in the best interest of every store owner to make it as seamless as possible.

That said, the default checkout fields in Magento 2 are not always enough for the requirements of your store. You might need to gather some additional info about customers to improve your service. In this case, you should know how to add custom fields in Magento 2 checkout.

If you haven't figured out how to do it yet, this is exactly what you'll learn today.

By default, Magento 2 doesn't provide an option to add custom checkout fields via the admin panel. That's why you'll need to do it programmatically by following the below steps. So, let's get right to them.

1. Create a New Module

For starters, make up your mind as to what field you want you add. For instance, you may go for the "delivery_date" field. The next step is to create a new module using the following method.

Create a Magefan/DeliveryDate/registration.php file and add the following content:

<?php
use \Magento\Framework\Component\ComponentRegistrar;
ComponentRegistrar::register(ComponentRegistrar::MODULE,
'Magefan_Deliverydate', __DIR__);

Similarly, add the code below to the file Magefan/DeliveryDate/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="Magefan_DeliveryDate" setup_version="1.0.0">
      <sequence>
           <module name="Magento_Sales"/>
      </sequence>
  </module>
</config>

2. Update Some Tables

Now you need to add the column "delivery_date" to such tables as "sales_order", "sales_order_grid" and "quote". Since the customers will enter the date, the field type has to be DateTime.

If you want to add a different type of field and save its content as a string, you can adjust the following code to match your needs.

<?php
namespace Magefan\DeliveryDate\Setup;
use Magento\Framework\Setup\InstallSchemaInterface;
use Magento\Framework\Setup\ModuleContextInterface;
use Magento\Framework\Setup\SchemaSetupInterface;

class InstallSchema implements InstallSchemaInterface
{
   public function install(SchemaSetupInterface $setup, ModuleContextInterface $context)
  {
       $installer = $setup;
       $installer->startSetup();

       $installer->getConnection()->addColumn(
           $installer->getTable('quote'),
           'delivery_date',
           [
               'type' => 'datetime',
               'nullable' => false,
               'comment' => 'Delivery Date',
           ]
       );

       $installer->getConnection()->addColumn(
           $installer->getTable('sales_order'),
           'delivery_date',
           [
               'type' => 'datetime',
               'nullable' => false,
               'comment' => 'Delivery Date',
           ]
       );

       $installer->getConnection()->addColumn(
           $installer->getTable('sales_order_grid'),
           'delivery_date',
           [
               'type' => 'datetime',
               'nullable' => false,
               'comment' => 'Delivery Date',
           ]
       );

       $setup->endSetup();
   }
}

3. Add a Custom Field to the Checkout Page

Once the previous steps are completed, you are ready to add the field to the checkout page. You'll need to work with the frontend here. To be more exact you have to create a Magefan/DeliveryDate/etc/frontend/di.xml file in the module and run the plugin:

<?xml version="1.0"?>
<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_delivery_date_field"

type
="Magefan\DeliveryDate\Plugin\Checkout\LayoutProcessorPlugin"
sortOrder="10"/>
  </type>
</config>

Then add the following content to the Magefan/DeliveryDate/Plugin/Checkout/LayoutProcessorPlugin.php file:

<?php
namespace Magefan\DeliveryDate\Plugin\Checkout;

class LayoutProcessorPlugin
{
   /**
    * @param \Magento\Checkout\Block\Checkout\LayoutProcessor $subject
    * @param array $jsLayout
    * @return array
   */
   public function afterProcess(
       \Magento\Checkout\Block\Checkout\LayoutProcessor $subject,
       array $jsLayout
   ) {

$jsLayout['components']['checkout']['children']['steps']['children']['shipping-step']['children']
['shippingAddress']['children']['before-form']['children']['delivery_date'] = [
           'component' => 'Magento_Ui/js/form/element/date',
           'config' => [
               'customScope' => 'shippingAddress',
               'template' => 'ui/form/field',
               'elementTmpl' => 'ui/form/element/date',
               'options' => [],
               'id' => 'delivery_date'
           ],
           'dataScope' => 'shippingAddress.delivery_date',
           'label' => __('Delivery Date'),
           'provider' => 'checkoutProvider',
           'visible' => true,
           'validation' => [],
           'sortOrder' => 200,
           'id' => 'delivery_date'
       ];


       return $jsLayout;
   }
}

At this point, you'll already be able to see the custom field on the storefront.

Add custom fields to Magento 2 checkout

4. Collect the Data Added to the New Field

Along with that, you also need to save the data your customers enter so that you can process orders accordingly. All Magento 2 checkout pages are created with knockout JS and API. So at first, you need to add some .js files to Magefan/DeliveryDate/view/frontend/requirejs-config.js:

var config = {
   config: {
       mixins: {
           'Magento_Checkout/js/action/place-order': {
               'Magefan_DeliveryDate/js/order/place-order-mixin': true
           },
       }
  }
};

Now, using Ajax, add the value of your field to Magefan/DeliveryDate/view/frontend/web/js/order/place-order-mixin.js:

define([
   'jquery',
   'mage/utils/wrapper',
   'Magento_CheckoutAgreements/js/model/agreements-assigner',
   'Magento_Checkout/js/model/quote',
   'Magento_Customer/js/model/customer',
   'Magento_Checkout/js/model/url-builder',
   'mage/url',
   'Magento_Checkout/js/model/error-processor',
   'uiRegistry'
], function (
   $,
   wrapper,
   agreementsAssigner,
   quote,
   customer,
   urlBuilder,
   urlFormatter,
   errorProcessor,
   registry
) {
   'use strict';

   return function (placeOrderAction) {

       /** Override default place order action and add agreement_ids to request */
       return wrapper.wrap(placeOrderAction, function (originalAction, paymentData, messageContainer) {
           agreementsAssigner(paymentData);
           var isCustomer = customer.isLoggedIn();
           var quoteId = quote.getQuoteId();

           var url = urlFormatter.build('mfcustom/quote/save');

           var deliveryDate = $('[name="delivery_date"]').val();

           if (deliveryDate) {

               var payload = {
                   'cartId': quoteId,
                   'delivery_date': deliveryDate,
                   'is_customer': isCustomer
               };

               if (!payload.delivery_date) {
                   return true;
               }

               var result = true;

               $.ajax({
                   url: url,
                   data: payload,
                   dataType: 'text',
                   type: 'POST',
               }).done(
                   function (response) {
                       result = true;
                   }
              ).fail(
                   function (response) {
                       result = false;
                       errorProcessor.process(response);
                   }
               );
           }

           return originalAction(paymentData, messageContainer);
       });
   };
});

The next step is to create the Magefan/DeliveryDate/Controller/Quote/Save.php file:

<?php

namespace Magezon\Deliverydate\Controller\Quote;

class Save extends \Magento\Framework\App\Action\Action
{
protected $quoteIdMaskFactory;

protected $quoteRepository;

public function __construct(
\Magento\Framework\App\Action\Context $context,
\Magento\Quote\Model\QuoteIdMaskFactory $quoteIdMaskFactory,
\Magento\Quote\Api\CartRepositoryInterface $quoteRepository
) {
parent::__construct($context);
$this->quoteRepository = $quoteRepository;
$this->quoteIdMaskFactory = $quoteIdMaskFactory;
}

/**
* @return \Magento\Framework\Controller\Result\Raw
*/
public function execute()
{
$post = $this->getRequest()->getPostValue();
if ($post) {
$cartId = $post['cartId'];
$deliveryDate = $post['delivery_date'];
$loggin = $post['is_customer'];

if ($loggin === 'false') {
$cartId = $this->quoteIdMaskFactory->create()->load($cartId, 'masked_id')->getQuoteId();
}

$quote = $this->quoteRepository->getActive($cartId);
if (!$quote->getItemsCount()) {
throw new NoSuchEntityException(__('Cart %1 doesn\'t contain products', $cartId));
}

$quote->setData('delivery_date', $deliveryDate);
$this->quoteRepository->save($quote);
}
}
}

You also need to enable Ajax to send the collected data to the controller. You can do it the following way:

<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="urn:magento:framework:App/etc/routes.xsd">
<router id="standard">
<route id="mgzcustom" frontName="mgzcustom">
<module name="Magezon_Deliverydate" />
</route>
</router>
</config>

Finally, you can finish this step by creating Magefan/DeliveryDate/etc/events.xml:

<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

xsi:noNamespaceSchemaLocation
="urn:magento:framework:Event/etc/events.xsd">
   <event name="sales_model_service_quote_submit_before">
       <observer name="save_delivery_date_to_order"
instance="Magefan\DeliveryDate\Observer\SaveToOrder" />
  </event>
</config>

5. Show the New Value in the Sales Order Grid

Last but not least, you also have to display the created value in the sales order grid. You need to create the file sales_order_grid.xml and add a Delivery Date column to Magefan/DeliveryDate/view/adminhtml/ui_component/sales_order_grid.xml:

<?xml version="1.0"?>
<listing xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

xsi:noNamespaceSchemaLocation
="urn:magento:module:Magento_Ui:etc/ui_configuration.xsd">
   <columns name="sales_order_columns">
       <column name="delivery_date" class="Magefan\DeliveryDate\Ui\Component\Listing\Column\DeliveryDate">
           <argument name="data" xsi:type="array">
               <item name="config" xsi:type="array">
                   <item name="visible" xsi:type="boolean">true</item>
                   <item name="label" xsi:type="string" translate="true">Delivery Date</item>
                   <item name="dateFormat" xsi:type="string">Y-MM-dd</item>
                   <item name="component" xsi:type="string">Magento_Ui/js/grid/columns/date</item>
                   <item name="dataType" xsi:type="string">date</item>
              </item>
          </argument>
      </column>
  </columns>
</listing>

For the data to be displayed in this column, you need to make the following changes in Magefan/DeliveryDate/Ui/Component/Listing/Column/DeliveryDate.php:

<?php
namespace Magefan\DeliveryDate\Ui\Component\Listing\Column;

use \Magento\Sales\Api\OrderRepositoryInterface;
use \Magento\Framework\View\Element\UiComponent\ContextInterface;
use \Magento\Framework\View\Element\UiComponentFactory;
use \Magento\Ui\Component\Listing\Columns\Column;
use \Magento\Framework\Api\SearchCriteriaBuilder;

class DeliveryDate extends Column
{
   protected $_orderRepository;
   protected $_searchCriteria;

   public function __construct(ContextInterface $context, UiComponentFactory
$uiComponentFactory, OrderRepositoryInterface $orderRepository,
SearchCriteriaBuilder $criteria, array $components = [], array $data = [])
  {
       $this->_orderRepository = $orderRepository;
       $this->_searchCriteria = $criteria;
       parent::__construct($context, $uiComponentFactory, $components, $data);
   }

   public function prepareDataSource(array $dataSource)
  {
       if (isset($dataSource['data']['items'])) {
           foreach ($dataSource['data']['items'] as & $item) {

               $order = $this->_orderRepository->get($item["entity_id"]);
               $date = $order->getData("delivery_date");
               $item[$this->getData('name')] = $date;
           }
       }

       return $dataSource;
   }
}

After completing these steps, you'll get the following result.

Custom field's data in Magento sales grid

So, did you manage to add a custom field to Magento 2 checkout?

Creating custom checkout fields in Magento 2 is not a task you can quickly cope with. It has many stages, making the entire process a bit complex. Nevertheless, the final result is worth your efforts. The more customer information you can gather, the better service you'll offer.

However, don't forget about the checkout page itself. You need to make it as user-friendly as possible. So, you might also want to add payment method icons to your checkout or set default payment method.