Magento 2: Create Product Attribute, Attribute Group/Tab & Attribute Set Programmatically

This article shows how you can programmatically add or create a new product attribute, product attribute group, and product attribute set in Magento 2.

In this article, we will be looking at the following:

  • Create Product Attribute
  • Update Product Attribute
  • Remove Product Attribute
  • Create Product Attribute Group/Tab
  • Add Product Attributes to the Attribute Group/Tab
  • Create Product Attribute Set
  • Add Attribute Group to Attribute Set
  • Add Attribute to all Attribute Sets

I will also show how you can add the product attributes from both Install Script and Upgrade Script:

  • Add product attribute from Install Script (InstallData.php)
  • Add product attribute from Upgrade Script (UpgradeData.php)

The example module name for this article is: Chapagain_ProductAttribute

Add/Create Product Attribute Using the Install Script

Here, we will be creating two product attributes:

  • My Custom Text (attribute code: chapagain_attribute_text_1)
  • My Custom SelectBox (attribute code: chapagain_attribute_select_1)

app/code/Chapagain/ProductAttribute/Setup/InstallData.php


<?php

namespace Chapagain\ProductAttribute\Setup;

use Magento\Eav\Setup\EavSetupFactory;
use Magento\Framework\Setup\InstallDataInterface;
use Magento\Framework\Setup\ModuleContextInterface;
use Magento\Framework\Setup\ModuleDataSetupInterface;

class InstallData implements InstallDataInterface
{
    /**
     * EAV setup factory
     *
     * @var \Magento\Eav\Setup\EavSetupFactory
     */
    protected $eavSetupFactory;

    /**
     * Constructor
     *
     * @param EavSetupFactory $eavSetupFactory
     */
    public function __construct(
        EavSetupFactory $eavSetupFactory,
    ) {
        $this->eavSetupFactory = $eavSetupFactory;
    }

    /**
     * {@inheritdoc}
     */
    public function install(
        ModuleDataSetupInterface $setup,
        ModuleContextInterface $context
    ) {
        $setup->startSetup();

        $eavSetup = $this->eavSetupFactory->create(['setup' => $setup]);
       
        /**
         * Insert/Create a simple text attribute
         */
        $eavSetup->addAttribute(
            \Magento\Catalog\Model\Product::ENTITY,
            'chapagain_attribute_text_1',
            [
                'type' => 'text',
                'backend' => '',
                'frontend' => '',
                'label' => 'My Custom Text',
                'input' => 'text',
                'class' => '',
                'source' => '',
                'global' => \Magento\Eav\Model\Entity\Attribute\ScopedAttributeInterface::SCOPE_STORE, // can also use \Magento\Catalog\Model\ResourceModel\Eav\Attribute::SCOPE_STORE, // scope of the attribute (global, store, website)
                'visible' => true,
                'required' => true,
                'user_defined' => false,
                'default' => '',
                'searchable' => false,
                'filterable' => false,
                'comparable' => false,
                'visible_on_front' => false,
                'used_in_product_listing' => true,
                'unique' => false,
                'apply_to' => ''
            ]
        );

        /**
         * Insert/Create a seletbox attribute with custom options
         */
        $eavSetup->addAttribute(
            \Magento\Catalog\Model\Product::ENTITY,
            'chapagain_attribute_select_1',
            [
                'type' => 'int', // data type to be saved in database table
                'backend' => '',
                'frontend' => '',
                'label' => 'My Custom Selectbox',
                'input' => 'select', // form element type displayed in the form
                'class' => '',
                'source' => 'Chapagain\ProductAttribute\Model\Config\Source\MyCustomOptions',
                'global' => \Magento\Eav\Model\Entity\Attribute\ScopedAttributeInterface::SCOPE_STORE, // can also use \Magento\Catalog\Model\ResourceModel\Eav\Attribute::SCOPE_STORE, // scope of the attribute (global, store, website)
                'visible' => true,
                'required' => true,
                'user_defined' => false,
                'default' => '',
                'searchable' => false,
                'filterable' => false,
                'comparable' => false,
                'visible_on_front' => false,
                'used_in_product_listing' => true,
                'unique' => false,
                'apply_to' => ''
            ]
        );

        $setup->endSetup();
    }
}

For My Custom Selectbox (chapagain_attribute_select_1) attribute, we have defined a custom source file: Chapagain\ProductAttribute\Model\Config\Source\MyCustomOptions

Hence, we need to create the source file as well. This class file contains the list of options to be displayed in the select box attribute.

app/code/Chapagain/ProductAttribute/Model/Config/Source/MyCustomOptions.php


<?php

namespace Chapagain\ProductAttribute\Model\Config\Source;

class MyCustomOptions extends \Magento\Eav\Model\Entity\Attribute\Source\AbstractSource
{
    /**
     * Get all options
     *
     * @return array
     */
    public function getAllOptions()
    {
        if ($this->_options === null) {
            $this->_options = [
                ['value' => '', 'label' => __('Please Select')],
                ['value' => '1', 'label' => __('My Option 1')],
                ['value' => '2', 'label' => __('My Option 2')],
                ['value' => '3', 'label' => __('My Option 3')],
                ['value' => '4', 'label' => __('My Option 4')]
            ];
        }
        return $this->_options;
    }

    /**
     * Get text of the option value
     * 
     * @param string|integer $value
     * @return string|bool
     */
    public function getOptionValue($value) 
    { 
        foreach ($this->getAllOptions() as $option) {
            if ($option['value'] == $value) {
                return $option['label'];
            }
        }
        return false;
    }
}

Add/Create Product Attribute & Attribute Group Using the Install Script

In the code below, the following are done:

  • Create two product attributes:
    • My Custom Text (chapagain_attribute_text_1)
    • My Custom Selectbox (chapagain_attribute_text_1)
  • Create a new product attribute group named My Custom Group
  • Assign those two attributes to the newly created attribute group

app/code/Chapagain/ProductAttribute/Setup/InstallData.php


<?php

namespace Chapagain\ProductAttribute\Setup;

use Magento\Eav\Setup\EavSetupFactory;
use Magento\Framework\Setup\InstallDataInterface;
use Magento\Framework\Setup\ModuleContextInterface;
use Magento\Framework\Setup\ModuleDataSetupInterface;

class InstallData implements InstallDataInterface
{
    /**
     * EAV setup factory
     *
     * @var \Magento\Eav\Setup\EavSetupFactory
     */
    protected $eavSetupFactory;

    /**
     * Constructor
     *
     * @param EavSetupFactory $eavSetupFactory
     */
    public function __construct(
        EavSetupFactory $eavSetupFactory
    ) {
        $this->eavSetupFactory = $eavSetupFactory;
    }

    /**
     * {@inheritdoc}
     */
    public function install(
        ModuleDataSetupInterface $setup,
        ModuleContextInterface $context
    ) {
        $setup->startSetup();

        $eavSetup = $this->eavSetupFactory->create(['setup' => $setup]);

        /**
         * Insert/Create a simple text attribute
         */
        $eavSetup->addAttribute(
            \Magento\Catalog\Model\Product::ENTITY,
            'chapagain_attribute_text_1',
            [
                'type' => 'text',
                'backend' => '',
                'frontend' => '',
                'label' => 'My Custom Text',
                'input' => 'text',
                'class' => '',
                'source' => '',
                'global' => \Magento\Eav\Model\Entity\Attribute\ScopedAttributeInterface::SCOPE_STORE, // can also use \Magento\Catalog\Model\ResourceModel\Eav\Attribute::SCOPE_STORE, // scope of the attribute (global, store, website)
                'visible' => true,
                'required' => true,
                'user_defined' => false,
                'default' => '',
                'searchable' => false,
                'filterable' => false,
                'comparable' => false,
                'visible_on_front' => false,
                'used_in_product_listing' => true,
                'unique' => false,
                'apply_to' => ''
            ]
        );

        /**
         * Insert/Create a seletbox attribute with custom options
         */
        $eavSetup->addAttribute(
            \Magento\Catalog\Model\Product::ENTITY,
            'chapagain_attribute_select_1',
            [
                'type' => 'int', // data type to be saved in database table
                'backend' => '',
                'frontend' => '',
                'label' => 'My Custom Selectbox',
                'input' => 'select', // form element type displayed in the form
                'class' => '',
                'source' => 'Chapagain\ProductAttribute\Model\Config\Source\MyCustomOptions',
                'global' => \Magento\Eav\Model\Entity\Attribute\ScopedAttributeInterface::SCOPE_STORE, // can also use \Magento\Catalog\Model\ResourceModel\Eav\Attribute::SCOPE_STORE, // scope of the attribute (global, store, website)
                'visible' => true,
                'required' => true,
                'user_defined' => false,
                'default' => '',
                'searchable' => false,
                'filterable' => false,
                'comparable' => false,
                'visible_on_front' => false,
                'used_in_product_listing' => true,
                'unique' => false,
                'apply_to' => ''
            ]
        );

        // get default attribute set id
        $attributeSetId = $eavSetup->getDefaultAttributeSetId(\Magento\Catalog\Model\Product::ENTITY);
        $attributeGroupName = 'My Custom Group';

        // your custom attribute group/tab
        $eavSetup->addAttributeGroup(
            \Magento\Catalog\Model\Product::ENTITY,
            $attributeSetId,
            $attributeGroupName, // attribute group name
            100 // sort order
        );

        // add attribute to group
        $eavSetup->addAttributeToGroup(
            \Magento\Catalog\Model\Product::ENTITY,
            $attributeSetId,
            $attributeGroupName, // attribute group
            'chapagain_attribute_text_1', // attribute code
            10 // sort order
        );

        // add attribute to group
        $eavSetup->addAttributeToGroup(
            \Magento\Catalog\Model\Product::ENTITY,
            $attributeSetId,
            $attributeGroupName, // attribute group
            'chapagain_attribute_select_1', // attribute code
            20 // sort order
        );

        $setup->endSetup();
    }
}

Add/Create Product Attribute, Attribute Group & Attribute Set Using the Install Script

In the code below, we do the following:

  • Create a new attribute set named: MyCustomAttributeSet
  • Create a product attribute named My Custom Attr with the attribute code: chapagain_attribute_2
  • Create a new product attribute group named My Custom Group 2 in all the attribute sets present in the Magento store
  • Assign the product attribute chapagain_attribute_2 to the attribute group My Custom Group 2 in all the attribute sets available in the Magento store

app/code/Chapagain/ProductAttribute/Setup/InstallData.php


<?php

namespace Chapagain\ProductAttribute\Setup;

use Magento\Eav\Setup\EavSetupFactory;
use Magento\Eav\Model\Entity\Attribute\SetFactory;
use Magento\Framework\Setup\InstallDataInterface;
use Magento\Framework\Setup\ModuleContextInterface;
use Magento\Framework\Setup\ModuleDataSetupInterface;

class InstallData implements InstallDataInterface
{
    /**
     * EAV setup factory
     *
     * @var \Magento\Eav\Setup\EavSetupFactory
     */
    protected $eavSetupFactory;

    /**
     * Attribute set factory
     *
     * @var SetFactory
     */
    protected $attributeSetFactory;

    /**
     * Constructor
     *
     * @param EavSetupFactory $eavSetupFactory
     * @param SetFactory $attributeSetFactory
     */
    public function __construct(
        EavSetupFactory $eavSetupFactory,
        SetFactory $attributeSetFactory
    ) {
        $this->eavSetupFactory = $eavSetupFactory;
        $this->attributeSetFactory = $attributeSetFactory;
    }

    /**
     * {@inheritdoc}
     */
    public function install(
        ModuleDataSetupInterface $setup,
        ModuleContextInterface $context
    ) {
        $setup->startSetup();

        $eavSetup = $this->eavSetupFactory->create(['setup' => $setup]);

        /**
         * Create a New Attribute Set
         */
        $attributeSet = $this->attributeSetFactory->create();
        $entityTypeId = $eavSetup->getEntityTypeId(\Magento\Catalog\Model\Product::ENTITY);
        $attributeSetId = $eavSetup->getDefaultAttributeSetId($entityTypeId); // default attribute set
        $data = [
            'attribute_set_name' => 'MyCustomAttributeSet',
            'entity_type_id' => $entityTypeId,
            'sort_order' => 100,
        ];
        $attributeSet->setData($data);
        $attributeSet->validate();
        $attributeSet->save();
        $attributeSet->initFromSkeleton($attributeSetId);
        $attributeSet->save();

        // add a new attribute 
        // and assign it to the "MyCustomAttributeSet" attribute set
        $eavSetup->addAttribute(
            \Magento\Catalog\Model\Product::ENTITY,
            'chapagain_attribute_2',
            [
                'type' => 'text',
                'backend' => '',
                'frontend' => '',
                'label' => 'My Custom Attr',
                'input' => 'text',
                'class' => '',
                'source' => '',
                'global' => \Magento\Eav\Model\Entity\Attribute\ScopedAttributeInterface::SCOPE_GLOBAL, // can also use \Magento\Catalog\Model\ResourceModel\Eav\Attribute::SCOPE_STORE, // scope of the attribute (global, store, website)
                'visible' => true,
                'required' => true,
                'user_defined' => false,
                'default' => '',
                'searchable' => false,
                'filterable' => false,
                'comparable' => false,
                'visible_on_front' => false,
                'used_in_product_listing' => true,
                'unique' => false,
                'apply_to' => '',
                'attribute_set' => 'MyCustomAttributeSet' // assigning the attribute to the attribute set "MyCustomAttributeSet"
            ]
        );

        /**
         * Create a custom attribute group in all attribute sets
         * And, Add attribute to that attribute group for all attribute sets
         */

        // we are going to add this attribute to all attribute sets
        $attributeCode = 'chapagain_attribute_2';

        //
        $attributeGroupName = 'My Custom Group 2';

        // get the catalog_product entity type id/code
        $entityTypeId = $eavSetup->getEntityTypeId(\Magento\Catalog\Model\Product::ENTITY);

        // get the attribute set ids of all the attribute sets present in your Magento store
        $attributeSetIds = $eavSetup->getAllAttributeSetIds($entityTypeId);

        foreach ($attributeSetIds as $attributeSetId) {
            $eavSetup->addAttributeGroup(
                $entityTypeId, 
                $attributeSetId, 
                $attributeGroupName, 
                200 // sort order
            );
            
            // get the newly create attribute group id
            $attributeGroupId = $eavSetup->getAttributeGroupId($entityTypeId, $attributeSetId, $attributeGroupName);

            // add attribute to group
            $eavSetup->addAttributeToGroup(
                $entityTypeId, // can also use: \Magento\Catalog\Model\Product::ENTITY instead of $entityTypeId
                $attributeSetId,
                $attributeGroupName, // attribute group
                $attributeCode, // this is defined above as 'chapagain_attribute_2
                null // sort order, can be integer value like 10 or 30, etc.
            );
        }

        $setup->endSetup();
    }
}

Add/Create/Update/Remove Product Attribute Using the Upgrade Script

The following is done in the code below:

  • Create a new attribute named: My Yes/No Attribute with the attribute code: chapagain_attribute_bool_1
  • Update the “Frontend Label” of the attribute chapagain_attribute_text_1
  • Remove an attribute with attribute code your_attribute_code_to_delete
  • version_compare() function is used to run the code on each version upgrade

app/code/Chapagain/ProductAttribute/Setup/UpgradeData.php


<?php

namespace Chapagain\ProductAttribute\Setup;

use Magento\Eav\Setup\EavSetupFactory;
use Magento\Framework\Setup\UpgradeDataInterface;
use Magento\Framework\Setup\ModuleContextInterface;
use Magento\Framework\Setup\ModuleDataSetupInterface;

/**
 * @codeCoverageIgnore
 */
class UpgradeData implements UpgradeDataInterface
{
    /**
     * EAV setup factory
     *
     * @var \Magento\Eav\Setup\EavSetupFactory
     */
    protected $eavSetupFactory;

    /**
     * Constructor
     *
     * @param EavSetupFactory $eavSetupFactory
     */
    public function __construct(
        EavSetupFactory $eavSetupFactory
    ) {
        $this->eavSetupFactory = $eavSetupFactory;
    }

    /**
     * {@inheritdoc}
     */
    public function upgrade(ModuleDataSetupInterface $setup, ModuleContextInterface $context)
    {
        $setup->startSetup();

        $eavSetup = $this->eavSetupFactory->create(['setup' => $setup]);
        
        /**
         * run this code if the module version stored in database is less than 1.0.1
         * i.e. the code is run while upgrading the module from version 1.0.0 to 1.0.1
         */ 
        if (version_compare($context->getVersion(), '1.0.1') < 0) {
            $eavSetup->addAttribute(
                \Magento\Catalog\Model\Product::ENTITY,
                'chapagain_attribute_bool_1',   // product attribute code
                [
                    'type' => 'int', // datatype of the attribute
                    'backend' => '',  
                    'frontend' => '',
                    'label' => 'My Yes/No Attribute', // label of the attribute 
                    'input' => 'select',  // form element of the attribute
                    'class' => '', 
                    'source' => 'Magento\Eav\Model\Entity\Attribute\Source\Boolean', // define the source of the attribute (for select and multiselect attribute input type)
                    'global' => \Magento\Catalog\Model\ResourceModel\Eav\Attribute::SCOPE_GLOBAL, // scope of the attribute (global, store, website)
                    'visible' => true,
                    'required' => false,
                    'user_defined' => false,
                    'default' => 0,
                    'searchable' => false,
                    'filterable' => false,
                    'comparable' => false,
                    'visible_on_front' => false,
                    'used_in_product_listing' => false,
                    'unique' => false,
                    'apply_to' => ''
                ]
            );
        }

        /**
         * here, we are updating the attribute label 
         * while upgrading to module version 1.0.2
         */ 
        if (version_compare($context->getVersion(), '1.0.2') < 0) {
            $eavSetup->updateAttribute(
                \Magento\Catalog\Model\Product::ENTITY,
                'chapagain_attribute_text_1', // attribute code to update
                'frontend_label', // attribute field to update
                'My Custom Text Modified' // value to update
            );

            // Another way to update attribute
            $eavSetup->updateAttribute(
                \Magento\Catalog\Model\Product::ENTITY,
                'chapagain_attribute_select_1', // attribute code
                [
                    'frontend_label' => 'My Selectbox Modified'
                    // field name => value to update
                ]
            );
        }

        if (version_compare($context->getVersion(), '1.0.3') < 0) {
            /**
             * Remove attribute
             */
            $eavSetup->removeAttribute(
                \Magento\Catalog\Model\Product::ENTITY,
                'your_attribute_code_to_delete' // attribute code to remove
            );
        }

        $setup->endSetup();
    }
}

Increase the module version number in etc/module.xml

After updating the Upgrade setup code, you also need to ensure that you have upgraded the module version number in your module’s etc/module.xml file.

Run setup upgrade

Finally, run the setup:upgrade command that will run the Install/Upgrade Script code above.


php bin/magento setup:upgrade

Hope this helps. Thanks.