{"id":5966,"date":"2016-08-23T12:33:52","date_gmt":"2016-08-23T12:33:52","guid":{"rendered":"https:\/\/blog.mageworx.com\/?p=5966"},"modified":"2022-05-16T12:08:42","modified_gmt":"2022-05-16T12:08:42","slug":"an-easy-way-to-add-a-fieldset-with-fields-to-the-ui-form","status":"publish","type":"post","link":"https:\/\/www.mageworx.com\/blog\/easy-way-to-add-a-fieldset-with-fields-to-the-ui-form","title":{"rendered":"Easy Way to Add a Fieldset with Fields to the UI-Form"},"content":{"rendered":"\n<!-- SEO Ultimate (http:\/\/www.seodesignsolutions.com\/wordpress-seo\/) - Code Inserter module -->\n<!-- Google Tag Manager (noscript) -->\r\n<noscript><iframe src=\"https:\/\/www.googletagmanager.com\/ns.html?id=GTM-5DTCW7B8\"\r\nheight=\"0\" width=\"0\" style=\"display:none;visibility:hidden\"><\/iframe><\/noscript>\r\n<!-- End Google Tag Manager (noscript) -->\n<!-- \/SEO Ultimate -->\n\n<span class=\"span-reading-time rt-reading-time\" style=\"display: block;\"><span class=\"rt-label rt-prefix\">Reading Time: <\/span> <span class=\"rt-time\"> 5<\/span> <span class=\"rt-label rt-postfix\">minutes<\/span><\/span><p>In this article, we are going to create a simple module that will add a fieldset with fields in the product <a href=\"https:\/\/www.mageworx.com\/magento2-order-editor-extension.html\">editing<\/a> UI-form. Also, we will create an observer to intercept this data during the product saving.<\/p>\n<p>First, we need to create a Vendor_Product module:<\/p>\n<p>1. Create a directory app\/code\/Vendor\/Product<br \/>\n2. Create a registration file app\/code\/Vendor\/Product\/registration.php with the following content:<\/p>\n<pre class=\"lang:default decode:true\">    &lt;?php\n    \\Magento\\Framework\\Component\\ComponentRegistrar::register(\n        \\Magento\\Framework\\Component\\ComponentRegistrar::MODULE,\n        'Vendor_Product',\n        __DIR__\n    );\n    ?&gt;<\/pre>\n<p>Create a composer file (if you plan to transfer the module) app\/code\/Vendor\/Module\/composer.json :<\/p>\n<pre class=\"lang:default decode:true \">    {\n        \"name\": \"vendor\/module-product\",\n        \"description\": \"N\/A\",\n        \"type\": \"magento2-module\",\n        \"version\": \"1.0.0\",\n        \"license\": [\n            \"OSL-3.0\",\n            \"AFL-3.0\"\n        ],\n        \"autoload\": {\n            \"files\": [\n                \"registration.php\"\n            ],\n            \"psr-4\": {\n                \"Vendor\\\\Product\\\\\": \"\"\n            }\n        }\n    }<\/pre>\n<p>Now, create the module\u2019s main XML-file app\/code\/Vendor\/Product\/etc\/module.xml with the dependency from the Magento_Catalog module because our modal window will be added to its form:<\/p>\n<pre class=\"lang:default decode:true\">    &lt;?xml version=\"1.0\"?&gt;\n    &lt;config xmlns:xsi=\"https:\/\/www.w3.org\/2001\/XMLSchema-instance\" xsi:noNamespaceSchemaLocation=\"urn:magento:framework:Module\/etc\/module.xsd\"&gt;\n        &lt;module name=\"Vendor_Product\" setup_version=\"1.0.0\"&gt;\n            &lt;sequence&gt;\n                &lt;module name=\"Magento_Catalog\"\/&gt;\n            &lt;\/sequence&gt;\n        &lt;\/module&gt;\n    &lt;\/config&gt;<\/pre>\n<p>Enable the module by entering the following: bin\/magento module:enable Vendor_Product and bin\/magento setup:upgrade in the root Magento directory.<\/p>\n<p>Then, add the content of the module: the UI-form meta-data and virtual type for its addition.<\/p>\n<p>Create a file app\/code\/Vendor\/Product\/etc\/adminhtml\/di.xml. We are going to place a modifier inside:<\/p>\n<pre class=\"lang:default decode:true\">&lt;?xml version=\"1.0\"?&gt;\n&lt;config xmlns:xsi=\"https:\/\/www.w3.org\/2001\/XMLSchema-instance\" xsi:noNamespaceSchemaLocation=\"urn:magento:framework:ObjectManager\/etc\/config.xsd\"&gt;\n    &lt;virtualType name=\"Magento\\Catalog\\Ui\\DataProvider\\Product\\Form\\Modifier\\Pool\"&gt;\n        &lt;arguments&gt;\n            &lt;argument name=\"modifiers\" xsi:type=\"array\"&gt;\n                &lt;item name=\"custom-fieldset\" xsi:type=\"array\"&gt;\n                    &lt;item name=\"class\" xsi:type=\"string\"&gt;Vendor\\Product\\Ui\\DataProvider\\Product\\Form\\Modifier\\CustomFieldset&lt;\/item&gt;\n                    &lt;item name=\"sortOrder\" xsi:type=\"number\"&gt;10&lt;\/item&gt;\n                &lt;\/item&gt;\n            &lt;\/argument&gt;\n        &lt;\/arguments&gt;\n    &lt;\/virtualType&gt;\n&lt;\/config&gt;<\/pre>\n<p>The modifier is responsible for data addition and some manipulations with elements and UI-form components. There are 2 main methods that came from the modifier\u2019s interface (they should always present):<\/p>\n<pre class=\"lang:default decode:true\">    &lt;?php\n    \/**\n     * Copyright \u00a9 2016 Magento. All rights reserved.\n     * See COPYING.txt for license details.\n     *\/\n    namespace Magento\\Ui\\DataProvider\\Modifier;\n    \n    \/**\n     * Class ModifierInterface\n     *\/\n    interface ModifierInterface\n    {\n        \/**\n         * @param array $data\n         * @return array\n         *\/\n        public function modifyData(array $data);\n    \n        \/**\n         * @param array $meta\n         * @return array\n         *\/\n        public function modifyMeta(array $meta);\n    }\n    ?&gt;<\/pre>\n<p>We are going to use the modifyMeta method in this example. The modifyData method will be explained in the next article.<\/p>\n<p>Now, create the modifier file (app\/code\/Vendor\/Product\/Ui\/DataProvider\/Product\/Form\/Modifier\/CustomFieldset.php) with a custom fieldset for the product editing page and fill it with the fields:<\/p>\n<pre class=\"lang:default decode:true\">&lt;?php\nnamespace Vendor\\Product\\Ui\\DataProvider\\Product\\Form\\Modifier;\n\nuse Magento\\Catalog\\Model\\Locator\\LocatorInterface;\nuse Magento\\Catalog\\Ui\\DataProvider\\Product\\Form\\Modifier\\AbstractModifier;\nuse Magento\\Framework\\Stdlib\\ArrayManager;\nuse Magento\\Framework\\UrlInterface;\nuse Magento\\Ui\\Component\\Container;\nuse Magento\\Ui\\Component\\Form\\Fieldset;\nuse Magento\\Ui\\Component\\Form\\Element\\DataType\\Number;\nuse Magento\\Ui\\Component\\Form\\Element\\DataType\\Text;\nuse Magento\\Ui\\Component\\Form\\Element\\Input;\nuse Magento\\Ui\\Component\\Form\\Element\\Select;\nuse Magento\\Ui\\Component\\Form\\Element\\MultiSelect;\nuse Magento\\Ui\\Component\\Form\\Field;\n\nclass CustomFieldset extends AbstractModifier\n{\n\n    \/\/ Components indexes\n    const CUSTOM_FIELDSET_INDEX = 'custom_fieldset';\n    const CUSTOM_FIELDSET_CONTENT = 'custom_fieldset_content';\n    const CONTAINER_HEADER_NAME = 'custom_fieldset_content_header';\n\n    \/\/ Fields names\n    const FIELD_NAME_TEXT = 'example_text_field';\n    const FIELD_NAME_SELECT = 'example_select_field';\n    const FIELD_NAME_MULTISELECT = 'example_multiselect_field';\n\n    \/**\n     * @var \\Magento\\Catalog\\Model\\Locator\\LocatorInterface\n     *\/\n    protected $locator;\n\n    \/**\n     * @var ArrayManager\n     *\/\n    protected $arrayManager;\n\n    \/**\n     * @var UrlInterface\n     *\/\n    protected $urlBuilder;\n\n    \/**\n     * @var array\n     *\/\n    protected $meta = [];\n\n    \/**\n     * @param LocatorInterface $locator\n     * @param ArrayManager $arrayManager\n     * @param UrlInterface $urlBuilder\n     *\/\n    public function __construct(\n        LocatorInterface $locator,\n        ArrayManager $arrayManager,\n        UrlInterface $urlBuilder\n    ) {\n        $this-&gt;locator = $locator;\n        $this-&gt;arrayManager = $arrayManager;\n        $this-&gt;urlBuilder = $urlBuilder;\n    }\n\n    \/**\n     * Data modifier, does nothing in our example.\n     *\n     * @param array $data\n     * @return array\n     *\/\n    public function modifyData(array $data)\n    {\n        return $data;\n    }\n\n    \/**\n     * Meta-data modifier: adds ours fieldset\n     *\n     * @param array $meta\n     * @return array\n     *\/\n    public function modifyMeta(array $meta)\n    {\n        $this-&gt;meta = $meta;\n        $this-&gt;addCustomFieldset();\n\n        return $this-&gt;meta;\n    }\n\n    \/**\n     * Merge existing meta-data with our meta-data (do not overwrite it!)\n     *\n     * @return void\n     *\/\n    protected function addCustomFieldset()\n    {\n        $this-&gt;meta = array_merge_recursive(\n            $this-&gt;meta,\n            [\n                static::CUSTOM_FIELDSET_INDEX =&gt; $this-&gt;getFieldsetConfig(),\n            ]\n        );\n    }\n\n    \/**\n     * Declare ours fieldset config\n     *\n     * @return array\n     *\/\n    protected function getFieldsetConfig()\n    {\n        return [\n            'arguments' =&gt; [\n                'data' =&gt; [\n                    'config' =&gt; [\n                        'label' =&gt; __('Fieldset Title'),\n                        'componentType' =&gt; Fieldset::NAME,\n                        'dataScope' =&gt; static::DATA_SCOPE_PRODUCT, \/\/ save data in the product data\n                        'provider' =&gt; static::DATA_SCOPE_PRODUCT . '_data_source',\n                        'ns' =&gt; static::FORM_NAME,\n                        'collapsible' =&gt; true,\n                        'sortOrder' =&gt; 10,\n                        'opened' =&gt; true,\n                    ],\n                ],\n            ],\n            'children' =&gt; [\n                static::CONTAINER_HEADER_NAME =&gt; $this-&gt;getHeaderContainerConfig(10),\n                static::FIELD_NAME_TEXT =&gt; $this-&gt;getTextFieldConfig(20),\n                static::FIELD_NAME_SELECT =&gt; $this-&gt;getSelectFieldConfig(30),\n                static::FIELD_NAME_MULTISELECT =&gt; $this-&gt;getMultiSelectFieldConfig(40),\n            ],\n        ];\n    }\n\n    \/**\n     * Get config for header container\n     *\n     * @param int $sortOrder\n     * @return array\n     *\/\n    protected function getHeaderContainerConfig($sortOrder)\n    {\n        return [\n            'arguments' =&gt; [\n                'data' =&gt; [\n                    'config' =&gt; [\n                        'label' =&gt; null,\n                        'formElement' =&gt; Container::NAME,\n                        'componentType' =&gt; Container::NAME,\n                        'template' =&gt; 'ui\/form\/components\/complex',\n                        'sortOrder' =&gt; $sortOrder,\n                        'content' =&gt; __('You can write any text here'),\n                    ],\n                ],\n            ],\n            'children' =&gt; [],\n        ];\n    }\n\n    \/**\n     * Example text field config\n     *\n     * @param $sortOrder\n     * @return array\n     *\/\n    protected function getTextFieldConfig($sortOrder)\n    {\n        return [\n            'arguments' =&gt; [\n                'data' =&gt; [\n                    'config' =&gt; [\n                        'label' =&gt; __('Example Text Field'),\n                        'formElement' =&gt; Field::NAME,\n                        'componentType' =&gt; Input::NAME,\n                        'dataScope' =&gt; static::FIELD_NAME_TEXT,\n                        'dataType' =&gt; Number::NAME,\n                        'sortOrder' =&gt; $sortOrder,\n                    ],\n                ],\n            ],\n        ];\n    }\n\n    \/**\n     * Example select field config\n     *\n     * @param $sortOrder\n     * @return array\n     *\/\n    protected function getSelectFieldConfig($sortOrder)\n    {\n        return [\n            'arguments' =&gt; [\n                'data' =&gt; [\n                    'config' =&gt; [\n                        'label' =&gt; __('Options Select'),\n                        'componentType' =&gt; Field::NAME,\n                        'formElement' =&gt; Select::NAME,\n                        'dataScope' =&gt; static::FIELD_NAME_SELECT,\n                        'dataType' =&gt; Text::NAME,\n                        'sortOrder' =&gt; $sortOrder,\n                        'options' =&gt; $this-&gt;_getOptions(),\n                        'visible' =&gt; true,\n                        'disabled' =&gt; false,\n                    ],\n                ],\n            ],\n        ];\n    }\n\n    \/**\n     * Example multi-select field config\n     *\n     * @param $sortOrder\n     * @return array\n     *\/\n    protected function getMultiSelectFieldConfig($sortOrder)\n    {\n        return [\n            'arguments' =&gt; [\n                'data' =&gt; [\n                    'config' =&gt; [\n                        'label' =&gt; __('Options Multiselect'),\n                        'componentType' =&gt; Field::NAME,\n                        'formElement' =&gt; MultiSelect::NAME,\n                        'dataScope' =&gt; static::FIELD_NAME_MULTISELECT,\n                        'dataType' =&gt; Text::NAME,\n                        'sortOrder' =&gt; $sortOrder,\n                        'options' =&gt; $this-&gt;_getOptions(),\n                        'visible' =&gt; true,\n                        'disabled' =&gt; false,\n                    ],\n                ],\n            ],\n        ];\n    }\n\n    \/**\n     * Get example options as an option array:\n     *      [\n     *          label =&gt; string,\n     *          value =&gt; option_id\n     *      ]\n     *\n     * @return array\n     *\/\n    protected function _getOptions()\n    {\n        $options = [\n            1 =&gt; [\n                'label' =&gt; __('Option 1'),\n                'value' =&gt; 1\n            ],\n            2 =&gt; [\n                'label' =&gt; __('Option 2'),\n                'value' =&gt; 2\n            ],\n            3 =&gt; [\n                'label' =&gt; __('Option 3'),\n                'value' =&gt; 3\n            ],\n        ];\n\n        return $options;\n    }\n}\n?&gt;\n<\/pre>\n<p>In this example we need to take the existing UI-form meta-data and merge it (not rewrite!) with our new data:<\/p>\n<pre class=\"lang:default decode:true\">&lt;?php\n\/**\n * Merge existing meta-data with our meta-data (do not overwrite it!)\n *\n * @return void\n *\/\nprotected function addCustomFieldset()\n{\n    $this-&gt;meta = array_merge_recursive(\n        $this-&gt;meta,\n        [\n            static::CUSTOM_FIELDSET_INDEX =&gt; $this-&gt;getFieldsetConfig(),\n        ]\n    );\n}\n?&gt;<\/pre>\n<p>When done, add the new fieldset to the getFieldsetConfig method:<\/p>\n<pre class=\"lang:default decode:true\">&lt;?php\n\/**\n * Declare ours fieldset config\n *\n * @return array\n *\/\nprotected function getFieldsetConfig()\n{\n    return [\n        'arguments' =&gt; [\n            'data' =&gt; [\n                'config' =&gt; [\n                    'label' =&gt; __('Fieldset Title'),\n                    'componentType' =&gt; Fieldset::NAME,\n                    'dataScope' =&gt; static::DATA_SCOPE_PRODUCT, \/\/ save data in the product data\n                    'provider' =&gt; static::DATA_SCOPE_PRODUCT . '_data_source',\n                    'ns' =&gt; static::FORM_NAME,\n                    'collapsible' =&gt; true,\n                    'sortOrder' =&gt; 10,\n                    'opened' =&gt; true,\n                ],\n            ],\n        ],\n        'children' =&gt; [\n            static::CONTAINER_HEADER_NAME =&gt; $this-&gt;getHeaderContainerConfig(10),\n            static::FIELD_NAME_TEXT =&gt; $this-&gt;getTextFieldConfig(20),\n            static::FIELD_NAME_SELECT =&gt; $this-&gt;getSelectFieldConfig(30),\n            static::FIELD_NAME_MULTISELECT =&gt; $this-&gt;getMultiSelectFieldConfig(40),\n        ],\n    ];\n}\n?&gt;<\/pre>\n<p>We inherit from the abstract product UI-form modifier and use its namespace and data as a provider: &#8216;provider&#8217; =&gt; static::DATA_SCOPE_PRODUCT . &#8216;_data_source&#8217; (where DATA_SCOPE_PRODUCT is the &#8216;data.product&#8217; line).<\/p>\n<p>The componentType option is one of the main options and is responsible for the component type. The collapsible option is responsible for collapsing and expanding of our fieldset. And the open option defines wheter the fieldset will be open by default during the form drawing.<\/p>\n<p>Then, we consequently add a header to our fieldset in the getHeaderContainerConfig method and 3 examples of fields: text, select and multiselect. However, our product and form won&#8217;t receive data until we add it to the modifyData method. But we have the ability to transmit and intercept the data during saving.<\/p>\n<p>Let&#8217;s see how the form is look:<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter size-large wp-image-5971\" src=\"https:\/\/www.mageworx.com\/blog\/wp-content\/uploads\/2016\/08\/2b1929a8650f1b9af8bb590a7f062857-1024x464.png\" alt=\"\" width=\"1024\" height=\"464\" srcset=\"https:\/\/www.mageworx.com\/blog\/wp-content\/uploads\/2016\/08\/2b1929a8650f1b9af8bb590a7f062857-1024x464.png 1024w, https:\/\/www.mageworx.com\/blog\/wp-content\/uploads\/2016\/08\/2b1929a8650f1b9af8bb590a7f062857-150x68.png 150w, https:\/\/www.mageworx.com\/blog\/wp-content\/uploads\/2016\/08\/2b1929a8650f1b9af8bb590a7f062857-300x136.png 300w, https:\/\/www.mageworx.com\/blog\/wp-content\/uploads\/2016\/08\/2b1929a8650f1b9af8bb590a7f062857-768x348.png 768w, https:\/\/www.mageworx.com\/blog\/wp-content\/uploads\/2016\/08\/2b1929a8650f1b9af8bb590a7f062857.png 1898w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/>The data saving takes place inside the product controller file vendor\/magento\/module-catalog\/Controller\/Adminhtml\/Product\/Save.php in the main execute method. If everything has been done in the right way, then our data will be displayed correctly in the input data of this method:<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter size-large wp-image-5972\" src=\"https:\/\/www.mageworx.com\/blog\/wp-content\/uploads\/2016\/08\/e4ae1bb0ba65abb450638990b5ca0b40-1024x512.png\" alt=\"\" width=\"1024\" height=\"512\" srcset=\"https:\/\/www.mageworx.com\/blog\/wp-content\/uploads\/2016\/08\/e4ae1bb0ba65abb450638990b5ca0b40-1024x512.png 1024w, https:\/\/www.mageworx.com\/blog\/wp-content\/uploads\/2016\/08\/e4ae1bb0ba65abb450638990b5ca0b40-150x75.png 150w, https:\/\/www.mageworx.com\/blog\/wp-content\/uploads\/2016\/08\/e4ae1bb0ba65abb450638990b5ca0b40-300x150.png 300w, https:\/\/www.mageworx.com\/blog\/wp-content\/uploads\/2016\/08\/e4ae1bb0ba65abb450638990b5ca0b40-768x384.png 768w, https:\/\/www.mageworx.com\/blog\/wp-content\/uploads\/2016\/08\/e4ae1bb0ba65abb450638990b5ca0b40-400x200.png 400w, https:\/\/www.mageworx.com\/blog\/wp-content\/uploads\/2016\/08\/e4ae1bb0ba65abb450638990b5ca0b40.png 1920w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/>Note, if your product doesn&#8217;t have those attributes from the beginning, you should save them manually. You can do this in the observer.<\/p>\n<p>First, declare it in the app\/code\/Vendor\/Product\/etc\/adminhtml\/events.xml file (we are using the adminhtml scope because the form doesn&#8217;t exist on the front-end):<\/p>\n<pre class=\"lang:default decode:true\">&lt;?xml version=\"1.0\"?&gt;\n&lt;config xmlns:xsi=\"https:\/\/www.w3.org\/2001\/XMLSchema-instance\" xsi:noNamespaceSchemaLocation=\"urn:magento:framework:Event\/etc\/events.xsd\"&gt;\n    &lt;event name=\"catalog_product_save_after\"&gt;\n        &lt;observer name=\"save_example_data\" instance=\"Vendor\\Product\\Observer\\ProductSaveAfter\" \/&gt;\n    &lt;\/event&gt;\n&lt;\/config&gt;<\/pre>\n<p>Then, create the observer&#8217;s class that we pointed in the instance attribute \u2013 app\/code\/Vendor\/Product\/Observer\/ProductSaveAfter.php:<\/p>\n<pre class=\"lang:default decode:true\">&lt;?php\nnamespace Vendor\\Product\\Observer;\n\nuse \\Magento\\Framework\\Event\\ObserverInterface;\nuse \\Magento\\Framework\\Event\\Observer as EventObserver;\nuse Vendor\\Product\\Ui\\DataProvider\\Product\\Form\\Modifier\\CustomFieldset;\n\nclass ProductSaveAfter implements ObserverInterface\n{\n\n    \/**\n     * @param EventObserver $observer\n     *\/\n    public function execute(\\Magento\\Framework\\Event\\Observer $observer)\n    {\n        \/** @var \\Magento\\Catalog\\Model\\Product $product *\/\n        $product = $observer-&gt;getEvent()-&gt;getProduct();\n        if (!$product) {\n            return;\n        }\n\n        $exampleTextField = $product-&gt;getData(CustomFieldset::FIELD_NAME_TEXT);\n        $exampleSelectField = $product-&gt;getData(CustomFieldset::FIELD_NAME_SELECT);\n        $exampleMultiSelectField = $product-&gt;getData(CustomFieldset::FIELD_NAME_MULTISELECT);\n\n        \/\/ Manipulate data here\n    }\n}\n?&gt;<\/pre>\n<p>The data in the observer:<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter size-large wp-image-5973\" src=\"https:\/\/www.mageworx.com\/blog\/wp-content\/uploads\/2016\/08\/3e3a875415bdc91fcc11fb66e596c4c9-1024x508.png\" alt=\"\" width=\"1024\" height=\"508\" srcset=\"https:\/\/www.mageworx.com\/blog\/wp-content\/uploads\/2016\/08\/3e3a875415bdc91fcc11fb66e596c4c9-1024x508.png 1024w, https:\/\/www.mageworx.com\/blog\/wp-content\/uploads\/2016\/08\/3e3a875415bdc91fcc11fb66e596c4c9-150x74.png 150w, https:\/\/www.mageworx.com\/blog\/wp-content\/uploads\/2016\/08\/3e3a875415bdc91fcc11fb66e596c4c9-300x149.png 300w, https:\/\/www.mageworx.com\/blog\/wp-content\/uploads\/2016\/08\/3e3a875415bdc91fcc11fb66e596c4c9-768x381.png 768w, https:\/\/www.mageworx.com\/blog\/wp-content\/uploads\/2016\/08\/3e3a875415bdc91fcc11fb66e596c4c9.png 1920w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><\/p>\n<p>Now, you can call your own model from the observer and save data in it or modify it\u00a0as you wish.<\/p>\n<p><strong>Be careful! If the saving of your model is connected with the product saving, then it can lead to the recurssion.<\/strong><\/p>\n","protected":false},"excerpt":{"rendered":"<p><span class=\"span-reading-time rt-reading-time\" style=\"display: block;\"><span class=\"rt-label rt-prefix\">Reading Time: <\/span> <span class=\"rt-time\"> 5<\/span> <span class=\"rt-label rt-postfix\">minutes<\/span><\/span>In this article, we are going to create a simple module that will add a fieldset with fields in the product editing UI-form. Also, we will create an observer to intercept this data during the product saving. First, we need to create a Vendor_Product module: 1. Create a directory app\/code\/Vendor\/Product 2. Create a registration file [&hellip;]<\/p>\n","protected":false},"author":15,"featured_media":5836,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[255,425],"tags":[436],"class_list":{"0":"post-5966","1":"post","2":"type-post","3":"status-publish","4":"format-standard","5":"has-post-thumbnail","7":"category-magento-2","8":"category-magento-how-tos","9":"tag-developer-diaries"},"_links":{"self":[{"href":"https:\/\/www.mageworx.com\/blog\/wp-json\/wp\/v2\/posts\/5966","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.mageworx.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.mageworx.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.mageworx.com\/blog\/wp-json\/wp\/v2\/users\/15"}],"replies":[{"embeddable":true,"href":"https:\/\/www.mageworx.com\/blog\/wp-json\/wp\/v2\/comments?post=5966"}],"version-history":[{"count":6,"href":"https:\/\/www.mageworx.com\/blog\/wp-json\/wp\/v2\/posts\/5966\/revisions"}],"predecessor-version":[{"id":16008,"href":"https:\/\/www.mageworx.com\/blog\/wp-json\/wp\/v2\/posts\/5966\/revisions\/16008"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/www.mageworx.com\/blog\/wp-json\/wp\/v2\/media\/5836"}],"wp:attachment":[{"href":"https:\/\/www.mageworx.com\/blog\/wp-json\/wp\/v2\/media?parent=5966"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.mageworx.com\/blog\/wp-json\/wp\/v2\/categories?post=5966"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.mageworx.com\/blog\/wp-json\/wp\/v2\/tags?post=5966"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}