Create a product attribute

Adding a product attribute is one of the most popular operations in Commerce. Attributes are a powerful way to solve many practical tasks related to a product. There is a simple process of adding a dropdown-type attribute to a product.

In this video:

  • Add an attribute called clothing_material with the possible values: Cotton, Leather, Silk, Denim, Fur, and Wool
  • Make this attribute visible on the product view page, in bold text
  • Assign it to the Default attribute set and add a restriction
  • Add the new attribute

Who is this video for?

  • Developers new to commerce who need to learn how to create a product attribute programmatically

Video content

Adding a New Attribute to a Product. Adding a product attribute is one of the most popular operations in Magento for both 1 and 2. Attributes are powerful way to solve many practical tasks related to a product. This is quite a broad topic, but in this video we will discuss the simple process of adding a drop-down type attribute to a product. For this exercise, assume that the sample data set is installed. We will add an attribute called clothing_material with the possible values cotton, leather, silk, denim, fur, and wool. We will make this attribute visible on the product view page in bold text. We will assign it to the default attribute set and add a restriction that any bottom clothing, like slacks, cannot be the material, fur. So we’ll need to take the following steps to add the new attribute. One, create a new module, two, add an InstallData script, three, add a source model, four, add a backend model, five, add a frontend model, and six, execute the InstallData script and verify that it works.
Let’s go through each step.
One, create a new module. As Magenta is modular based, we start the process by creating a new module called Learning_ClothingMaterial.
You’ll create two files, etc module.xml and registration.php. We see module.xml here and registration.php here.
Number two, create an InstallData script.
Next, we need to create the InstallData script because adding an attribute technically adds records into several tables, such as eav_attribute and catalog_eav_attribute. This is data manipulation, not a schema change, therefore, we use InstallData instead of InstallSchema. So create the file app code \Learning\ClothingMaterial\Setup\InstallData. Let’s take a minute to look at the code. First of all, we need to use a special setup object, not the one that comes in as a parameter. This is because catalog is an EAV Entity. So to add an attribute, we have to use the $eavSetup rather than the standard one. This holds true for any EAV Entity in Magento 2, such as category, product, customer, and so on. This is why we added EavSetupFactory in a constructor.
For the install method, all we have to do is call the addAttribute method with three parameters, entity type, attribute code, and properties. Those properties define how an attribute behaves. A full list of properties can be seen in the catalog_eav_attribute and eav_attribute tables. Note that there is a mapping between the fields in those tables and the properties in the addAttribute method. To see all the mappings, you should look at Magento/Catalog/Model/ResourceModel/ Setup/PropertyMapper class. For now, we’ll just quickly go through the most important ones. Group means that we add the attribute to the attribute group General, which is present in all attribute sets. Type: Varchar means that the values will be stored in the catalog eav_varchar table. Label: A label of the attribute, that is how it will be rendered in the backend and on the frontend. Source, frontend, and backend: These are special classes associated with the attribute. A source model provides a list of options, frontend defines how it should be rendered on the frontend, and backend allows you to perform certain actions when an attribute is loaded or saved. In our example, it’ll be validation. Global defines the scope of its values, global website or store. visible_on_front is a flag that defines whether an attribute should be shown on the More Information tab on the frontend. is_html_allowed_on_front defines whether an attribute value may contain HTML.
Step three, add a source model.
Next, we need to create the source model under app, code, Learning, ClothingMaterial, Model, Attribute, Source, Material.
As the name implies, the getAllOptions method provides a list of all available options.
Step four, add a backend model.
Now we’ll create a backend model. We’ll put it in app, code, Learning, ClothingMaterial, Model, Attribute, Backend, Material.php.
In our example, we implement only the validate method.
The backend model may have beforeSave, afterSave, and afterLoad methods that allow the execution of some code at the moment an attribute is saved or loaded. The backend model is what makes attribute management a really powerful method of customization.
Note that we hard coded the AttributeSetId here for the sake of time. In other cases, it could be different. Make sure to check the eav_attribute_set table for the right ID.
Step five, add a frontend model.
And, finally, we create a frontend model to make our value bold.
As with the backend model, this is also a very simple class.
Now we can run our code and check the results.
After you run this, the new attribute should have been added to the database.
We see clothing_material down here, the primary key of 155, and its corresponding entry and catalog_eav_attribute with the primary key being 155.
Now let’s go to the backend, open any configurable product, and we should see that clothing_material drop-down.
We’ll set our filters to be a Configurable Product with the attributes set at Bottom.
We’ll select the first item.
First, we’ll set the Clothing Material to Fur and attempt to save our attribute.
We see that our backend model is executed successfully. So now we’ll set it to wool and save it.
Having saved the product, we’ll now move to the frontend.
It should be visible and in bold text. -

Code Sample

First create the folders, xml and PHP files that necessary:

  • app/code/Learning/ClothingMaterial/registration.php
  • app/code/Learning/ClothingMaterial/etc/module.xml
  • app/code/Learning/ClothingMaterial/Model/Attribute/Backend/Material.php
  • app/code/Learning/ClothingMaterial/Model/Attribute/Frontend/Material.php
  • app/code/Learning/ClothingMaterial/Model/Attribute/Source/Material.php
  • app/code/Learning/ClothingMaterial/Setup/installData.php



use Magento\Framework\Component\ComponentRegistrar;



If your module is using Declarative Schema, and most have since 2.3.0 you should omit setup_version. However if you have some legacy projects you may see this method used. See for more information.
<?xml version="1.0"?>
<config xmlns:xsi=""
    <module name="Learning_ClothingMaterial" setup_version="0.0.1"/>



namespace Learning\ClothingMaterial\Model\Attribute\Backend;

use Magento\Catalog\Model\Product;
use Magento\Eav\Model\Entity\Attribute\Backend\AbstractBackend;
use Magento\Framework\Exception\LocalizedException;

class Material extends AbstractBackend
     * @param Product @object
     * @throws LocalizedException
    public function validate($object)
        $value =$object->getData($this->getAttribute()->getAttributeCode());
        // Be sure to validate that your ID number it is likely to be different

        if (($object->getAttributeSetId() == 9) && ($value == 'fur')) {
            throw new LocalizedException(__('Bottoms cannot be fur'));



namespace Learning\ClothingMaterial\Model\Attribute\Frontend;

use Magento\Eav\Model\Entity\Attribute\Frontend\AbstractFrontend;
use Magento\Framework\DataObject;

class Material extends AbstractFrontend

    public function getValue(DataObject $object): string
        $value = $object->getData($this->getAttribute()->getAttributeCode());
        return "<b>$value</b>";




namespace Learning\ClothingMaterial\Model\Attribute\Source;

use Magento\Eav\Model\Entity\Attribute\Source\AbstractSource;

class Material extends AbstractSource
     * Get all options
     * @retrun array
    public function getAllOptions(): array
           $this->_options = [
               ['label'    => __('Cotton'),     'value' => 'cotton'],
               ['label'    => __('Leather'),    'value' => 'leather'],
               ['label'    => __('Silk'),       'value' => 'silk'],
               ['label'    => __('Fur'),        'value' => 'fur'],
               ['label'    => __('Wool'),       'value' => 'wool'],
       return $this->_options;



namespace Learning\ClothingMaterial\Setup;

use Learning\ClothingMaterial\Model\Attribute\Frontend\Material as Frontend;
use Learning\ClothingMaterial\Model\Attribute\Source\Material as Source;
use Learning\ClothingMaterial\Model\Attribute\Backend\Material as Backend;
use Magento\Catalog\Model\Product;
use Magento\Eav\Model\Entity\Attribute\ScopedAttributeInterface;
use Magento\Framework\Setup\InstallDataInterface;
use Magento\Framework\Setup\ModuleContextInterface;
use Magento\Framework\Setup\ModuleDataSetupInterface;

class InstallData implements InstallDataInterface
    protected $eavSetupFactory;

    public function __construct(
        \Magento\Eav\Setup\EavSetupFactory $eavSetupFactory
        $this->eavSetupFactory = $eavSetupFactory;

     * @param ModuleDataSetupInterface $setup
     * @param ModuleContextInterface $context
     * {@inheritDoc}
     * @SuppressWarnings(PHPMD.CyclomaticComplexity)
     * @suppressWarnings(PHPMD.ExessiveMethodLength)
     * @SuppressWarnings(PHPMD.NPathComplexity)
    public function install(ModuleDataSetupInterface $setup, ModuleContextInterface $context)
        $eavSetup = $this->eavSetupFactory->create();
                'group'         => 'General',
                'type'          => 'varchar',
                'label'         => 'Clothing Material',
                'input'         => 'select',
                'source'        => Source::class,
                'frontend'      => Frontend::class,
                'backend'       => Backend::class,
                'required'      => false,
                'sort_order'    => 50,
                'global'        => ScopedAttributeInterface::SCOPE_GLOBAL,
                'is_used_in_grid'               => false,
                'is_visible_in_grid'            => false,
                'is_filterable_in_grid'         => false,
                'visible'                       => true,
                'is_html_allowed_on_frontend'   => true,
                'visible_on_front'              => true,

Useful resources

Create a product attribute

Add a custom text field attribute