Magento 2 Product Attributes: Best Practices for a Clean Catalog
Product attributes look like simple form fields, but every decision about them has effects on indexing, search, layered navigation, imports and page speed. A catalog with three hundred attributes, half of them searchable and a third of them store view scoped, is slower and harder to maintain than it needs to be. These are the rules I follow when designing or cleaning up attributes on Magento Open Source and Adobe Commerce 2.4.8.
How attributes are stored
Products use the EAV model. Attribute definitions live in eav_attribute, with catalog specific properties in catalog_eav_attribute. Values are stored in one table per backend type: catalog_product_entity_varchar, _int, _decimal, _text and _datetime. Each value row has the attribute ID, the store ID and the product's entity ID, or row ID on Adobe Commerce.
Dropdown and multi select attributes store option IDs, with labels in eav_attribute_option_value per store. That is why a dropdown with consistent options is better for filtering than free text: the value is an integer that indexes and aggregates cleanly.
Pick the right input type
| Need | Input type | Backend type |
|---|---|---|
| Fixed list, used for filtering | Dropdown or visual/text swatch | int |
| Several values from a list | Multiple Select | varchar |
| Yes or no flag | Yes/No | int |
| Short free text | Text Field | varchar |
| Long text or HTML | Text Area or Page Builder | text |
| Numbers for comparison or sorting | Text Field with decimal validation, or Price | decimal |
| Dates | Date | datetime |
The most common mistake is using a text field for something that is really a list, such as material or brand. Free text ends up with "Cotton", "cotton" and "100% cotton" as separate values, and none of them can be a clean filter. Changing the input type later means migrating data between value tables, so choose carefully at the start.
Scope: global unless there is a reason
Each attribute has a scope of Global, Website or Store View. Store View scope is needed for text that is translated, such as name and description. For almost everything else, Global is the right choice.
- Store view scoped attributes can get a value row per store view. Imports and admin edits on a store view create override rows that hide later changes made on the default scope.
- Configurable attributes must be Global.
- Dropdown labels are already translatable per store view, even when the attribute scope is Global. You do not need Store View scope to translate option labels.
Website scope is mainly relevant for price, when the price scope is set to website, and for a few attributes such as status.
Storefront properties have a cost
Every flag under Storefront Properties adds work somewhere. Turn on only what the attribute needs.
- Use in Search. Adds the attribute to the product document in the search index. Many searchable attributes make the index bigger and reindex slower, and add noise to results. Search weight and synonyms are covered in search synonyms and relevance.
- Use in Layered Navigation. Adds an aggregation to category requests. Only for attributes that are useful filters and have a manageable number of options.
- Used in Product Listing. Loads the attribute with every product collection on category pages. Only needed if a listing template displays it. Leaving it on for many attributes slows down listings.
- Used for Sorting in Product Listing. Adds a sort option and makes the attribute sortable in the index.
- Use for Promo Rule Conditions. Makes the attribute available in cart and catalog price rule conditions.
- Visible on Catalog Pages on Storefront. Shows the attribute in the More Information tab on the product page.
- Add to Column Options and Use in Filter Options. Make the attribute available in the admin product grid. Handy, but the grid gets slower with many joined attributes.
Attribute sets
Attribute sets decide which attributes appear on a product's admin form. Use them to keep forms short and relevant: a set for clothing, a set for electronics, and so on. A product only has values for attributes in its set, and layered navigation only shows filters that have values on the listed products.
Do not create one set per category. A handful of sets that match real product families is easier to maintain. Moving a product to another set can drop values of attributes that are not in the new set, so plan the sets before the catalog grows.
Create attributes in code
Attributes created by hand in the admin exist only in the database where they were created. Create them with a data patch, so every environment gets the same definition and the change is versioned with the code. The full pattern, including reverting, is in creating a product attribute with a data patch. A compact example:
<?php
declare(strict_types=1);
namespace Vendor\Catalog\Setup\Patch\Data;
use Magento\Catalog\Model\Product;
use Magento\Eav\Model\Entity\Attribute\ScopedAttributeInterface;
use Magento\Eav\Model\Entity\Attribute\Source\Table;
use Magento\Eav\Setup\EavSetupFactory;
use Magento\Framework\Setup\ModuleDataSetupInterface;
use Magento\Framework\Setup\Patch\DataPatchInterface;
class AddMaterialAttribute implements DataPatchInterface
{
public function __construct(
private readonly ModuleDataSetupInterface $moduleDataSetup,
private readonly EavSetupFactory $eavSetupFactory
) {
}
public function apply(): self
{
$eavSetup = $this->eavSetupFactory->create(['setup' => $this->moduleDataSetup]);
$eavSetup->addAttribute(Product::ENTITY, 'material', [
'type' => 'int',
'label' => 'Material',
'input' => 'select',
'source' => Table::class,
'global' => ScopedAttributeInterface::SCOPE_GLOBAL,
'required' => false,
'user_defined' => true,
'searchable' => false,
'filterable' => 1,
'comparable' => false,
'visible_on_front' => true,
'used_in_product_listing' => false,
'group' => 'General',
'option' => ['values' => ['Cotton', 'Linen', 'Wool']],
]);
return $this;
}
public static function getDependencies(): array
{
return [];
}
public function getAliases(): array
{
return [];
}
}
Note that every storefront flag is set explicitly. Defaults in addAttribute() are not always what you expect, and being explicit documents the intent for the next developer. Set user_defined to true for attributes the business manages, so they can be edited and removed from the admin.
Naming and conventions
- Attribute codes are permanent in practice. They end up in imports, integrations, templates and GraphQL queries. Use lowercase, underscores, and a clear name such as
materialrather thanattr_23. - Do not reuse a code for a different meaning. Create a new attribute and migrate.
- Avoid codes that collide with existing product fields or GraphQL fields. Magento rejects some reserved codes in the admin, but a data patch gives less protection.
- Keep units out of values when you can. A decimal
weight_kgis easier to filter and sort than a text field with "1.5 kg".
Finding unused attributes
Older catalogs carry attributes from extensions that were removed, migrations and experiments. This query counts values per attribute in the int table. Repeat it for the other value tables:
SELECT a.attribute_code, COUNT(v.value_id) AS value_rows
FROM eav_attribute a
JOIN eav_entity_type t ON t.entity_type_id = a.entity_type_id
LEFT JOIN catalog_product_entity_int v ON v.attribute_id = a.attribute_id
WHERE t.entity_type_code = 'catalog_product'
AND a.backend_type = 'int'
AND a.is_user_defined = 1
GROUP BY a.attribute_id, a.attribute_code
ORDER BY value_rows ASC;
Attributes with no values and no references in code or templates are candidates for removal. Search the codebase and the import files for the code first. Remove them with a data patch that calls removeAttribute(), on staging first, and check the admin product form and the storefront afterwards.
Also look at catalog_eav_attribute for attributes that are searchable, filterable or used in listing without a reason. Turning those flags off is safe and often the quickest win for indexing time, together with the steps in OpenSearch setup and tuning.
FAQ
How many product attributes is too many in Magento 2?
There is no fixed limit. The cost comes from how many are searchable, filterable, used in listings and store view scoped. Hundreds of plain attributes are fine. Hundreds of searchable ones are not.
Should I use a dropdown or a text field for brand?
A dropdown. It gives consistent values, works as a layered navigation filter and aggregates efficiently in the search index. Text fields collect variations of the same value.
Can I change an attribute from text to dropdown later?
Not from the admin. The values live in a different table and must be migrated to option IDs with a script. It is doable, but plan it as a data migration with a backup.
Why are my admin changes not showing on a store view?
The attribute is store view scoped and that store view has its own value, often created by an import. Use the Use Default Value checkbox on the store view or remove the override rows.
I am available for freelance Magento and Adobe Commerce work, including catalog design and cleanup. If you need help with this, get in touch.
Related articles
Magento 2 Configurable Products Performance: What to Fix
Why configurable products slow down Magento 2 pages, admin saves and indexers, and how to fix it: variation counts, swatches, price indexing and data modeling.
Create a Product Attribute in Magento 2 with a Data Patch
Create Magento 2 product attributes with a data patch: text, select with options, custom source models, attribute sets, scope, revert and common mistakes.
Magento 2 Blackfire Profiling: Pages, CLI and Tests
Profile Magento 2 with Blackfire: probe and agent setup, profiling pages and bin/magento commands, reading call graphs and adding performance assertions.
Magento 2 Cache Types Explained: Clean, Flush, Custom
What each Magento 2 cache type stores, the real difference between cache:clean and cache:flush, invalidated caches, and how to add your own cache type.