How to Add a Field to Custom Options in Magento 2?

5
40842
Reading Time: 5 minutes

You all know how convenient it is to use products’ customizable options. We’ve got a powerful Advanced Product Options Suite that drastically expands the standard functionality of the customizable options. Custom fields are the basis of some of the extension’s functionality. These fields get added to specific option types, such as a checkbox or text area.

In the article, I’ll do my best to explain how you can add the necessary fields to the database and admin panel fast and easy. Let’s have an insight.

1. Create a new module

First, let’s create a MageWorx_Option module. We will get it all done using this module as an example.

Let’s create the following directory: app/code/MageWorx/Option. To register a module, we will need some standard files:

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


composer.json:
{
   "name": "mageworx/module-option",
   "description": "N/A",
   "require": {
       "magento/module-catalog" : ">=101.0.0 <104",
       "magento/module-ui"      : ">=100.1.0 <102"
   },
   "type": "magento2-module",
   "version": "1.0.0",
   "license": [
       "OSL-3.0",
       "AFL-3.0"
   ],
   "autoload": {
       "files": [
           "registration.php"
       ],
       "psr-4": {
           "MageWorx\\Option\\": ""
       }
   }
}

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="MageWorx_Option" setup_version="1.0.0">
       <sequence>
           <module name="Magento_Catalog"/>
           <module name="Magento_Ui"/>
       </sequence>
   </module>
</config>

2. Add our new fields to the database

For example, we wish to highlight one of the options on the front-end somehow. Let’s add a checkbox field for an option ‘Is Special Offer’ and a text field ‘Description’ for the selectable options values (dropdown, checkbox, radio button, multi-select).
First, we will need to add it to the database in the corresponding tables. For that, add the following file:  app/code/MageWorx/Option/Setup/InstallSchema.php.

<?php
namespace MageWorx\Option\Setup;

use Magento\Framework\Setup\InstallSchemaInterface;
use Magento\Framework\Setup\ModuleContextInterface;
use Magento\Framework\Setup\SchemaSetupInterface;
use Magento\Framework\DB\Ddl\Table;

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

       $setup->getConnection()->addColumn(
           $setup->getTable('catalog_product_option'),
           'is_special_offer',
           [
               'type'     => Table::TYPE_BOOLEAN,
               'unsigned' => true,
               'nullable' => false,
               'default'  => '0',
               'comment'  => 'Special Offer Flag',
           ]
       );

       $setup->getConnection()->addColumn(
           $setup->getTable('catalog_product_option_type_value'),
           'description',
           [
               'type'     => Table::TYPE_TEXT,
               'nullable' => true,
               'default'  => null,
               'comment'  => 'Description',
           ]
       );

       $setup->endSetup();
   }
}

The ‘Is Special Offer’ field will be disabled by default.

Next, we can install the module.

To do that, run the following commands in the console:

sudo -u www-data php bin/magento module:enable MageWorx_Option
sudo -u www-data php bin/magento setup:upgrade

3. Add logic to work with the backend

At this point, let’s add the pool-modifiers mechanism to our module, which Magento 2 uses to combine all the necessary features on a product page in the admin panel.

For that, let’s add the following file:

app/code/MageWorx/Option/etc/adminhtml/di.xml

<?xml version="1.0"?>

<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd">
   <virtualType name="Magento\CatalogStaging\Ui\DataProvider\Product\Form\Modifier\Pool" type="Magento\Ui\DataProvider\Modifier\Pool">
       <arguments>
           <argument name="modifiers" xsi:type="array">
               <item name="mageworx-option-all" xsi:type="array">
                   <item name="class" xsi:type="string">MageWorx\Option\Ui\DataProvider\Product\Form\Modifier\All</item>
                   <item name="sortOrder" xsi:type="number">71</item>
               </item>
           </argument>
       </arguments>
   </virtualType>
   <virtualType name="Magento\Catalog\Ui\DataProvider\Product\Form\Modifier\Pool" type="Magento\Ui\DataProvider\Modifier\Pool">
       <arguments>
           <argument name="modifiers" xsi:type="array">
               <item name="mageworx-option-all" xsi:type="array">
                   <item name="class" xsi:type="string">MageWorx\Option\Ui\DataProvider\Product\Form\Modifier\All</item>
                   <item name="sortOrder" xsi:type="number">71</item>
               </item>
           </argument>
       </arguments>
   </virtualType>
   <virtualType name="MageWorx\Option\Ui\DataProvider\Product\Form\Modifier\Pool" type="Magento\Ui\DataProvider\Modifier\Pool">
       <arguments>
           <argument name="modifiers" xsi:type="array">
           </argument>
       </arguments>
   </virtualType>
   <type name="MageWorx\Option\Ui\DataProvider\Product\Form\Modifier\All">
       <arguments>
           <argument name="pool" xsi:type="object">MageWorx\Option\Ui\DataProvider\Product\Form\Modifier\Pool</argument>
       </arguments>
   </type>
   <virtualType name="MageWorx\Option\Ui\DataProvider\Product\Form\Modifier\Pool">
       <arguments>
           <argument name="modifiers" xsi:type="array">
               <item name="mageworx-option-base" xsi:type="array">
                   <item name="class" xsi:type="string">MageWorx\Option\Ui\DataProvider\Product\Form\Modifier\Base</item>
                   <item name="sortOrder" xsi:type="number">72</item>
               </item>
           </argument>
       </arguments>
   </virtualType>
</config>

Now, it’s time to create our MageWorx\Option\Ui\DataProvider\Product\Form\Modifier\All class, which doesn’t add anything new by itself to the Customizable Options form on the product page. Using the dependency injection (DI), it will add the necessary fields on the product page.

In fact, our Advanced Product Options Suite adds about 40 fields and other complex structures, which get added with the help of more than 10 packages that are included within the extension. As we do not need such complex structure here, we will use solely one class-modificator ―

 MageWorx\Option\Ui\DataProvider\Product\Form\Modifier\Base. 

You might wonder, why we specify the following sort_order = 71. This all can be explained by the standard Magento 2 functionality, where fields for Customizable Options get added under sort_order = 70.

Below, take a look at the MageWorx\Option\Ui\DataProvider\Product\Form\Modifier\All class, which is presented by a regular iterator:

app/code/MageWorx/Option/Ui/DataProvider/Product/Form/Modifier/All.php

<?php
namespace MageWorx\Option\Ui\DataProvider\Product\Form\Modifier;

use Magento\Catalog\Ui\DataProvider\Product\Form\Modifier\AbstractModifier;
use Magento\Ui\DataProvider\Modifier\PoolInterface;

class All extends AbstractModifier implements \Magento\Ui\DataProvider\Modifier\ModifierInterface
{
   /**
    * @var PoolInterface
    */
   protected $pool;

   /**
    * @var array
    */
   protected $meta = [];

   /**
    * @param PoolInterface $pool
    */
   public function __construct(
       PoolInterface $pool
   ) {
       $this->pool = $pool;
   }

   /**
    * {@inheritdoc}
    */
   public function modifyData(array $data)
   {
       /** @var \Magento\Ui\DataProvider\Modifier\ModifierInterface $modifier */
       foreach ($this->pool->getModifiersInstances() as $modifier) {
           $data = $modifier->modifyData($data);
       }

       return $data;
   }

   /**
    * {@inheritdoc}
    */
   public function modifyMeta(array $meta)
   {
       $this->meta = $meta;

       /** @var \Magento\Ui\DataProvider\Modifier\ModifierInterface $modifier */
       foreach ($this->pool->getModifiersInstances() as $modifier) {
           $this->meta = $modifier->modifyMeta($this->meta);
       }

       return $this->meta;
   }
}

Basically, now is the time to create a file, which will add our fields to the Customizable Options form:

app/code/MageWorx/Option/Ui/DataProvider/Product/Form/Modifier/Base.php

<?php
namespace MageWorx\Option\Ui\DataProvider\Product\Form\Modifier;

use Magento\Catalog\Ui\DataProvider\Product\Form\Modifier\AbstractModifier;
use Magento\Catalog\Ui\DataProvider\Product\Form\Modifier\CustomOptions;
use Magento\Ui\Component\Form\Element\Input;
use Magento\Ui\Component\Form\Element\Checkbox;
use Magento\Ui\Component\Form\Element\DataType\Text;
use Magento\Ui\Component\Form\Field;

class Base extends AbstractModifier
{
   /**
    * @var array
    */
   protected $meta = [];

   /**
    * {@inheritdoc}
    */
   public function modifyData(array $data)
   {
       return $data;
   }

   /**
    * {@inheritdoc}
    */
   public function modifyMeta(array $meta)
   {
       $this->meta = $meta;

       $this->addFields();

       return $this->meta;
   }

   /**
    * Adds fields to the meta-data
    */
   protected function addFields()
   {
       $groupCustomOptionsName    = CustomOptions::GROUP_CUSTOM_OPTIONS_NAME;
       $optionContainerName       = CustomOptions::CONTAINER_OPTION;
       $commonOptionContainerName = CustomOptions::CONTAINER_COMMON_NAME;

       // Add fields to the option
       $this->meta[$groupCustomOptionsName]['children']['options']['children']['record']['children']
       [$optionContainerName]['children'][$commonOptionContainerName]['children'] = array_replace_recursive(
           $this->meta[$groupCustomOptionsName]['children']['options']['children']['record']['children']
           [$optionContainerName]['children'][$commonOptionContainerName]['children'],
           $this->getOptionFieldsConfig()
       );

       // Add fields to the values
       $this->meta[$groupCustomOptionsName]['children']['options']['children']['record']['children']
       [$optionContainerName]['children']['values']['children']['record']['children'] = array_replace_recursive(
           $this->meta[$groupCustomOptionsName]['children']['options']['children']['record']['children']
           [$optionContainerName]['children']['values']['children']['record']['children'],
           $this->getValueFieldsConfig()
       );
   }

   /**
    * The custom option fields config
    *
    * @return array
    */
   protected function getOptionFieldsConfig()
   {
       $fields['is_special_offer'] = $this->getSpecialOfferFieldConfig();

       return $fields;
   }

   /**
    * The custom option fields config
    *
    * @return array
    */
   protected function getValueFieldsConfig()
   {
       $fields['description'] = $this->getDescriptionFieldConfig();

       return $fields;
   }

   /**
    * Get special offer field config
    *
    * @return array
    */
   protected function getSpecialOfferFieldConfig()
   {
       return [
           'arguments' => [
               'data' => [
                   'config' => [
                       'label'         => __('Is Special Offer'),
                       'componentType' => Field::NAME,
                       'formElement'   => Checkbox::NAME,
                       'dataScope'     => 'is_special_offer',
                       'dataType'      => Text::NAME,
                       'sortOrder'     => 65,
                       'valueMap'      => [
                           'true'  => '1',
                           'false' => '0'
                       ],
                   ],
               ],
           ],
       ];
   }

   /**
    * Get description field config
    *
    * @return array
    */
   protected function getDescriptionFieldConfig()
   {
       return [
           'arguments' => [
               'data' => [
                   'config' => [
                       'label' => __('Description'),
                       'componentType' => Field::NAME,
                       'formElement'   => Input::NAME,
                       'dataType'      => Text::NAME,
                       'dataScope'     => 'description',
                       'sortOrder'     => 41
                   ],
               ],
           ],
       ];
   }
}

What we need to do here is recursively add a necessary config for ‘Is Special Offer’ and ‘Description’ fields at the right place. Pay attention to the last two methods that actually realize the config of the added fields. For ‘Is Special Offer we use checkbox, and for ‘Description’ ― text input.

As our fields in the database are located in the ‘catalog_product_option’ and ‘catalog_product_option_type_value’ fields, Magento 2 itself will add them to the form providing that we specify ‘dataScope’ correctly. 

It is important to use different ‘sortOrder’ to avoid replacing the standard Customizable Options fields. After playing around with various ‘sortOrder’ variants, you can arrange fields in the order that suits you the most.

Besides, the fields config allows you to add various inline validations. For example, in our Advanced Product Options extension, the ‘Cost’ field is implemented as follows:

 'label'             => __('Cost'),
   'componentType'     => Field::NAME,
   'formElement'       => Input::NAME,
   'dataScope'         => ‘cost’,
   'dataType'          => Number::NAME,
   'validation'        => [
       'validate-number'          => true,
       'validate-zero-or-greater' => true,
   ]

Then, clear cache:

sudo -u www-data php bin/magento cache:clean

The only thing left is to open a product we are interested in, fill in the necessary fields, and save it. The final result will look something like that:

Wrap Up

Magento 2 offers an extremely convenient mechanism of expanding customizable options with practically unlimited functionality. That is what we eagerly use in our extension and certainly recommend you.

Book a Live Demo with Mageworx

Alexey is MageWorx Magento Certified Developer. He has contributed a lot of his time and efforts to making our Advanced Product Options extension (both Magento 1 & Magento 2) one of the top solutions on the market.? Always ready to start a new adventure, he has already visited more than 20 countries, and counting…

5 COMMENTS

  1. Hi Alexey,

    Thanks for your post.

    But I was trying to show a radio button instead of the the checkbox so that only one default value should be selected, but unable to do so.

    I was trying by using ‘formElement’ => Radio::NAME .

    Can you please suggest how can that be achieved?

    ,

    • Hello Vikramraj,
      Thank you for taking the time to leave a comment. Unfortunately, you won’t be able to achieve that using a radio button. We use custom code in our Advanced Product Options extension to achieve such functionality.

  2. i placed a field next to custom option as show above is saves data,but as example i have to upload 500 product using csv, so can i set values of custom field with custom options using csv file in product import?

    is it possible to import product using csv with custom field data?

    • Hello Sandip,
      Thank you for your comment. To achieve that, you would need to customize the Magento import files. It’s likely to allow you to import new fields.

Leave a Reply to Ellie Yantsan Cancel reply

Please enter your comment!
Please enter your name here