Magento 2: How To Add Additional Options In Cart Items

Here, I will show you how to add additional options in the Magento 2 cart items. In the below example, I am going to show you, how to add an input field on the product view page and while adding the product to cart, will also add that field value into the cart as an additional option.

Step 1: Create a Simple Magento 2 Module

Create a module.xml file in the below file path to define the new module,

File Path: <Magento_2_Root>/app/code/BlogTreat/CustomOption/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="BlogTreat_CustomOption" setup_version="1.0.0">
    </module>
</config>

Next, create a registration.php file in the below file path,

File Path: <Magento_2_Root>/app/code/BlogTreat/CustomOption/registration.php

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

Step 2: Add Input Field on Product View Page

For adding the input field on the product view page, we have to override the product view page template file.

Create catalog_product_view.xml file in the below file path and add the following code,

File Path: <Magento_2_Root> /app/code/BlogTreat/CustomOption/view/frontend/layout/catalog_product_view.xml

<?xml version="1.0"?>
<page layout="1column" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:View/Layout/etc/page_configuration.xsd">
    <body>
        <referenceBlock name="product.info.addtocart">
            <action method="setTemplate">
                <argument name="template" xsi:type="string">BlogTreat_CustomOption::catalog/product/view/addtocart.phtml</argument>
            </action>
        </referenceBlock>

        <referenceBlock name="product.info.addtocart.additional">
            <action method="setTemplate">
                <argument name="template" xsi:type="string">BlogTreat_CustomOption::catalog/product/view/addtocart.phtml</argument>
            </action>
        </referenceBlock>
    </body>
</page>

Create addtocart.phtml file in the below file path and add the following code,

File Path: <Magento_2_Root> /app/code/BlogTreat/CustomOption/view/frontend/templates/catalog/product/view/addtocart.phtml

<?php
/**
 * Copyright © 2016 Magento. All rights reserved.
 * See COPYING.txt for license details.
 */

// @codingStandardsIgnoreFile

/** @var $block \Magento\Catalog\Block\Product\View */
?>
<?php $_product = $block->getProduct(); ?>
<?php $buttonTitle = __('Add to Cart'); ?>
<?php if ($_product->isSaleable()): ?>
<div class="box-tocart">
    <div class="fieldset">
        <?php if ($block->shouldRenderQuantity()): ?>
        <div class="field qty">
            <label class="label" for="qty"><span><?php /* @escapeNotVerified */ echo __('Qty') ?></span></label>
            <div class="control">
                <input type="number"
                       name="qty"
                       id="qty"
                       maxlength="12"
                       value="<?php /* @escapeNotVerified */ echo $block->getProductDefaultQty() * 1 ?>"
                       title="<?php /* @escapeNotVerified */ echo __('Qty') ?>" class="input-text qty"
                       data-validate="<?php echo $block->escapeHtml(json_encode($block->getQuantityValidators())) ?>"
                       />
            </div>
        </div>
        <!-- Custom Input Field -->
        <div>
            <input
                type="text"
                name="blogtreat[feedback]"
                id="remarks"
                maxlength="255"
                placeholder="Feedback"
            />
        </div>
        <!-- Custom Input Field -->
        <br>
        <?php endif; ?>
        <div class="actions">
            <button type="submit"
                    title="<?php /* @escapeNotVerified */ echo $buttonTitle ?>"
                    class="action primary tocart"
                    id="product-addtocart-button">
                <span><?php /* @escapeNotVerified */ echo $buttonTitle ?></span>
            </button>
            <?php echo $block->getChildHtml('', true) ?>
        </div>
    </div>
</div>
<?php endif; ?>
<script type="text/x-magento-init">
    {
        "#product_addtocart_form": {
            "Magento_Catalog/product/view/validation": {
                "radioCheckboxClosest": ".nested"
            }
        }
    }
</script>
<?php if (!$block->isRedirectToCartEnabled()) : ?>
<script type="text/x-magento-init">
    {
        "#product_addtocart_form": {
            "catalogAddToCart": {
                "bindSubmit": false
            }
        }
    }
</script>
<?php endif; ?>

Reference Image:

Magento 2: How To Add Additional Options In Cart Items

Step 3: Assign Observers to Events

Create events.xml file in the below file path and add the following code,

File Path: <Magento_2_Root> /app/code/BlogTreat/CustomOption/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="checkout_cart_product_add_after">
        <observer name="blogtreat_customoption_checkout_cart_product_add_after" instance="BlogTreat\CustomOption\Observer\CheckoutCartProductAddAfterObserver" />
    </event>
    <event name="sales_model_service_quote_submit_before">
        <observer name="blogtreat_customoption_sales_model_service_quote_submit_before" instance="BlogTreat\CustomOption\Observer\SalesModelServiceQuoteSubmitBeforeObserver" />
    </event>
</config>

In the above code, I have assigned the observer CheckoutCartProductAddAfterObserver to the event checkout_cart_product_add_after and SalesModelServiceQuoteSubmitBeforeObserver to sales_model_service_quote_submit_before.

Step 4: Create an Observer

To add a custom option to quote, create CheckoutCartProductAddAfterObserver.php file in the below file path and add the following code in it

File Path: <Magento_2_Root> /app/code/BlogTreat/CustomOption/Observer/CheckoutCartProductAddAfterObserver.php

<?php
namespace BlogTreat\CustomOption\Observer;

use Magento\Framework\Event\Observer as EventObserver;
use Magento\Framework\Event\ObserverInterface;

class CheckoutCartProductAddAfterObserver implements ObserverInterface
{
    /**
     * @var \Magento\Framework\View\LayoutInterface
     */
    protected $_layout;
    
    /**
     * @var \Magento\Store\Model\StoreManagerInterface
     */
    protected $_storeManager;
    
    protected $_request;

    /**
     * @param \Magento\Store\Model\StoreManagerInterface $storeManager
     * @param \Magento\Framework\View\LayoutInterface $layout
     */
    public function __construct(
        \Magento\Store\Model\StoreManagerInterface $storeManager,
        \Magento\Framework\View\LayoutInterface $layout,
        \Magento\Framework\App\RequestInterface $request
    ) {
        $this->_layout = $layout;
        $this->_storeManager = $storeManager;
        $this->_request = $request;
    }

    /**
     * @param EventObserver $observer
     * @return void
     */
    public function execute(EventObserver $observer)
    {
        /* @var \Magento\Quote\Model\Quote\Item $item */
        $item = $observer->getQuoteItem();
        $additionalOptions = array();
        if ($additionalOption = $item->getOptionByCode('additional_options')) {
            $additionalOptions = (array) unserialize($additionalOption->getValue());
        }
        $post = $this->_request->getParam('blogtreat');
        if(is_array($post)) {
            foreach($post as $key => $value) {
                if($key == '' || $value == '') {
                    continue;
                }
                $additionalOptions[] = [
                    'label' => $key,
                    'value' => $value
                ];
            }
        }
        if(count($additionalOptions) > 0) {
            $item->addOption([
                'code' => 'additional_options',
                'value' => serialize($additionalOptions)
            ]);
        }
    }
}

Create another observer file SalesModelServiceQuoteSubmitBeforeObserver.php in the below file path with the following code,

File Path: <Magento_2_Root> /app/code/BlogTreat/CustomOption/Observer/SalesModelServiceQuoteSubmitBeforeObserver.php

The below code is used to copy the custom option from quote_item to order_item.

<?php
namespace BlogTreat\CustomOption\Observer;

use Magento\Framework\Event\Observer as EventObserver;
use Magento\Framework\Event\ObserverInterface;

class SalesModelServiceQuoteSubmitBeforeObserver implements ObserverInterface
{
    private $quoteItems = [];

    private $quote = null;

    private $order = null;

    /**
     * @param EventObserver $observer
     * @return void
     */
    public function execute(EventObserver $observer)
    {
        $this->quote = $observer->getQuote();
        $this->order = $observer->getOrder();

        /* @var  \Magento\Sales\Model\Order\Item $orderItem */
        foreach($this->order->getItems() as $orderItem)
        {
            if(!$orderItem->getParentItemId() && $orderItem->getProductType() == \Magento\Catalog\Model\Product\Type::TYPE_SIMPLE)
            {
                if($quoteItem = $this->getQuoteItemById($orderItem->getQuoteItemId())) {
                    if ($additionalOptionsQuote = $quoteItem->getOptionByCode('additional_options')) {
                        if($additionalOptionsOrder = $orderItem->getProductOptionByCode('additional_options')) {
                            $additionalOptions = array_merge($additionalOptionsQuote, $additionalOptionsOrder);
                        }
                        else {
                            $additionalOptions = $additionalOptionsQuote;
                        }
                        if(count($additionalOptions) > 0) {
                            $options = $orderItem->getProductOptions();
                            $options['additional_options'] = unserialize($additionalOptions->getValue());
                            $orderItem->setProductOptions($options);
                        }
                    }
                }
            }
        }
    }
    private function getQuoteItemById($id)
    {
        if(empty($this->quoteItems)) {
            /* @var  \Magento\Quote\Model\Quote\Item $item */
            foreach($this->quote->getItems() as $item) {
                if(!$item->getParentItemId() && $item->getProductType() == \Magento\Catalog\Model\Product\Type::TYPE_SIMPLE) {
                    $this->quoteItems[$item->getId()] = $item;
                }
            }
        }
        if(array_key_exists($id, $this->quoteItems)) {
            return $this->quoteItems[$id];
        }
        return null;
    }
}

Finally, the custom module that will add additional options to Magento 2 Cart and forward the custom information items across orders, invoices, and shipments.

Now, enable and activate the module through the following CLI command,

php bin/magento setup:upgrade
php bin/magento setup:di:compile
php bin/magento cache:clean
php bin/magento cache:flush

Hope this blog helped you to add a custom option in Magento2 cart item.

Leave a Reply

Your email address will not be published. Required fields are marked *