Planning Attribute Sets Strategically Instead of Letting Them Sprawl
AI generated
M2
di.xml
Magento 2 · EAV · Attribute Sets · Governance
Planning attribute sets strategically
instead of letting them sprawl organically

In most agency projects, attribute sets grow uncontrolled: every new product type gets its own set, naming conventions are missing, and after two years nobody can say which of the eighty sets are actually still used in production. Teams that instead align attribute sets with product types and business requirements before creation, generate them programmatically through Data Patches and keep them under a clear governance process, keep the EAV structure maintainable, performant and comprehensible for new developers.

18 min read EavSetup · Data Patch · Attribute Set Repository · Console Command Magento 2.4.8 · PHP 8.4

1. Sprawl: how attribute sets grow uncontrolled in agency projects

Almost every grown Magento project shows the same picture: a three digit number of attribute sets, and nobody can explain anymore what half of them were originally meant for. The reason is rarely technical, it is organizational. A new product type is added, a merchandiser clicks "Add New Attribute Set" in the admin, types a name like "Set 2" or "Test Winter Collection" and saves it. Nobody checks whether an existing set with one additional attribute group would have been enough. After a year of agency operation with several editors, interns and external service providers, fifty to a hundred attribute sets easily accumulate, and most of them are never used again.

The real problem is not the number itself, it is the missing plan behind it. Every attribute set in Magento 2 is a combination of entity type, attribute groups and a list of assigned attributes with individual sorting. Without a deliberate structure, this combination forms randomly, depending on who happened to have admin access. The result: duplicate sets with almost identical content, sets with not a single assigned product, and sets whose name reveals nothing about their purpose. Every one of these situations costs time later, whether during onboarding of new developers, during a product import, or while debugging why an attribute is not shown on the frontend.

2. Planning attribute sets strategically: product types and requirements first

A viable approach starts before the first click in the admin: which product types actually exist in the catalog, and which of them differ enough on a business level that they need their own attributes? A clothing retailer with simple and configurable products typically needs far fewer standalone attribute sets than expected, if variants are modeled through attribute groups instead of entirely new sets. The rule of thumb: a new set is worth it when the set of relevant attributes differs significantly, not when a single additional attribute is missing. For the latter, it is enough to add that attribute to the existing set.

In practice, a short planning step before every new module has proven useful: a table with product type, the attributes that are truly required on a business level, and the question of whether an existing set already covers these attributes fully or almost fully. Only when this check comes back negative should a new attribute set be created. This mindset prevents the most common root cause of sprawl: the assumption that every new product category automatically needs its own set, even though category and attribute set are completely independent concepts in Magento 2. Categories drive navigation, attribute sets drive which fields a product shows in the admin form and potentially on the frontend.

3. Attribute groups and sort_order within a set

Inside an attribute set, attribute groups provide the structure that becomes visible as tabs or sections in the product editor. Each group has its own sort_order, which determines the order of the tabs, and each attribute inside a group also has its own sort_order, which determines the order of the fields inside that tab. This dual sorting is often overlooked in practice because the default import and the default attributes already come with sensible values. But as soon as custom groups are added, for example "Technical Data" or "SEO Attributes", the sorting has to be set deliberately, otherwise new fields end up at the bottom of the list and get overlooked by editors.

Well structured groups also reduce the need for entirely new attribute sets: instead of creating a separate set with the same base attributes for every product type, an additional, optional group in the existing set can hold the deviating fields. The following example shows how a new group with explicit sorting is created via EavSetup inside a Data Patch, including the assignment of an existing attribute with its own sort_order inside the group.


<?php

declare(strict_types=1);

namespace Mironsoft\AttributeSetGuard\Setup\Patch\Data;

use Magento\Eav\Setup\EavSetup;
use Magento\Eav\Setup\EavSetupFactory;
use Magento\Framework\Setup\ModuleDataSetupInterface;
use Magento\Framework\Setup\Patch\DataPatchInterface;
use Magento\Catalog\Model\Product;

/**
 * Adds a dedicated "Technical Data" attribute group with explicit sort_order
 * to the existing "Default" attribute set instead of creating a new set.
 */
final class AddTechnicalDataGroup implements DataPatchInterface
{
    public function __construct(
        private readonly ModuleDataSetupInterface $moduleDataSetup,
        private readonly EavSetupFactory $eavSetupFactory,
    ) {
    }

    /**
     * Creates the group and assigns an existing attribute with its own sort_order.
     *
     * @return void
     */
    public function apply(): void
    {
        $this->moduleDataSetup->getConnection()->startSetup();

        /** @var EavSetup $eavSetup */
        $eavSetup = $this->eavSetupFactory->create(['setup' => $this->moduleDataSetup]);

        $entityTypeId = $eavSetup->getEntityTypeId(Product::ENTITY);
        $attributeSetId = $eavSetup->getAttributeSetId($entityTypeId, 'Default');

        // Group sort_order 25 places the new tab after "Advanced Pricing"
        $groupId = $eavSetup->addAttributeGroup(
            $entityTypeId,
            $attributeSetId,
            'Technical Data',
            25
        );

        // Attribute sort_order 10 is the first field inside the new group
        $eavSetup->addAttributeToGroup(
            $entityTypeId,
            $attributeSetId,
            $groupId,
            'material',
            10
        );

        $this->moduleDataSetup->getConnection()->endSetup();
    }

    /**
     * @return array<int, string>
     */
    public static function getDependencies(): array
    {
        return [];
    }

    /**
     * @return array<int, string>
     */
    public function getAliases(): array
    {
        return [];
    }
}

4. Cloning vs. creating from scratch: choosing the right base

When a new product type genuinely justifies its own attribute set on business grounds, the next question is whether to create it from scratch or clone an existing set. Magento 2 offers a "Based On" field for this in the admin's "Add New Attribute Set" form, which internally uses the initFromSkeleton() method of the Magento\Eav\Model\Entity\Attribute\Set class. This method copies all attribute groups, including their sorting, plus all assigned attributes of the chosen skeleton set into the new set. This is the right choice in the vast majority of cases, because a new product type rarely needs completely different base attributes than the rest of the catalog, such as name, description, price or SEO fields.

Creating a set from scratch only makes sense when a set is meant to be deliberately minimal, for example for a very lean import of spare part or accessory data without the usual marketing attributes. In practice, cloning leads to noticeably more consistent attribute sets, because the base structure and sorting stay identical across all sets and only the deviating attributes need to be added. The following example clones an existing set programmatically, which is useful when this step needs to run reproducibly across multiple environments instead of being executed once in the admin.


<?php

declare(strict_types=1);

namespace Mironsoft\AttributeSetGuard\Setup\Patch\Data;

use Magento\Eav\Api\AttributeSetRepositoryInterface;
use Magento\Eav\Api\Data\AttributeSetInterfaceFactory;
use Magento\Eav\Model\Entity\Attribute\Set;
use Magento\Eav\Setup\EavSetupFactory;
use Magento\Framework\Setup\ModuleDataSetupInterface;
use Magento\Framework\Setup\Patch\DataPatchInterface;
use Magento\Catalog\Model\Product;

/**
 * Clones the "Default" attribute set into a new "Configurable - Clothing" set
 * using initFromSkeleton so groups, sort_order and attributes stay consistent.
 */
final class CloneClothingAttributeSet implements DataPatchInterface
{
    public function __construct(
        private readonly ModuleDataSetupInterface $moduleDataSetup,
        private readonly EavSetupFactory $eavSetupFactory,
        private readonly AttributeSetRepositoryInterface $attributeSetRepository,
        private readonly AttributeSetInterfaceFactory $attributeSetFactory,
    ) {
    }

    /**
     * Clones the skeleton set and persists the new set via the repository.
     *
     * @return void
     */
    public function apply(): void
    {
        $this->moduleDataSetup->getConnection()->startSetup();

        $eavSetup = $this->eavSetupFactory->create(['setup' => $this->moduleDataSetup]);
        $entityTypeId = (int) $eavSetup->getEntityTypeId(Product::ENTITY);
        $skeletonId = (int) $eavSetup->getAttributeSetId($entityTypeId, 'Default');

        /** @var Set $newSet */
        $newSet = $this->attributeSetFactory->create();
        $newSet->setEntityTypeId($entityTypeId);
        $newSet->setAttributeSetName('Configurable - Clothing');

        // Copies groups, sort_order and attribute assignments from the skeleton set
        $newSet->validate();
        $newSet->initFromSkeleton($skeletonId);
        $this->attributeSetRepository->save($newSet);

        $this->moduleDataSetup->getConnection()->endSetup();
    }

    /**
     * @return array<int, string>
     */
    public static function getDependencies(): array
    {
        return [];
    }

    /**
     * @return array<int, string>
     */
    public function getAliases(): array
    {
        return [];
    }
}

5. Programmatic creation with a Data Patch and EavSetup

Once it is clear that a new attribute set genuinely needs to be created from scratch, that creation belongs in a versioned Data Patch, not in a manual admin click. The difference is decisive for reproducibility: a Data Patch runs exactly once on every setup:upgrade in every environment, is part of code review and can be traced through the patch_list table. A set created manually in the admin, by contrast, exists only in the database it was created in, which leads to diverging states across staging, production and local development environments if nobody documents the manual creation.

Magento provides the Magento\Eav\Setup\EavSetup class for this task, injected through an EavSetupFactory. The addAttributeSet() method creates the set, addAttributeGroup() creates the associated groups, and addAttributeToGroup() assigns existing attributes to those groups. Important detail: addAttributeSet() already creates a default group internally, so custom calls to addAttributeGroup() are only needed for additional groups. The following Data Patch shows the full creation of a new attribute set for digital products, including a dedicated group and the assignment of several attributes with explicit sorting.


<?php

declare(strict_types=1);

namespace Mironsoft\AttributeSetGuard\Setup\Patch\Data;

use Magento\Eav\Setup\EavSetup;
use Magento\Eav\Setup\EavSetupFactory;
use Magento\Framework\Setup\ModuleDataSetupInterface;
use Magento\Framework\Setup\Patch\DataPatchInterface;
use Magento\Catalog\Model\Product;

/**
 * Creates a fresh "Digital - Licensed Product" attribute set from scratch
 * with its own group and explicit attribute sort_order.
 */
final class CreateDigitalProductAttributeSet implements DataPatchInterface
{
    /**
     * @param ModuleDataSetupInterface $moduleDataSetup Setup connection wrapper
     * @param EavSetupFactory $eavSetupFactory Factory for the EAV setup helper
     */
    public function __construct(
        private readonly ModuleDataSetupInterface $moduleDataSetup,
        private readonly EavSetupFactory $eavSetupFactory,
    ) {
    }

    /**
     * Creates the attribute set, a dedicated group and assigns attributes.
     *
     * @return void
     */
    public function apply(): void
    {
        $this->moduleDataSetup->getConnection()->startSetup();

        /** @var EavSetup $eavSetup */
        $eavSetup = $this->eavSetupFactory->create(['setup' => $this->moduleDataSetup]);
        $entityTypeId = $eavSetup->getEntityTypeId(Product::ENTITY);

        // Cloning the "Default" set as skeleton keeps base attributes consistent
        $skeletonId = $eavSetup->getAttributeSetId($entityTypeId, 'Default');
        $attributeSetId = $eavSetup->addAttributeSet(
            $entityTypeId,
            'Digital - Licensed Product',
            null,
            $skeletonId
        );

        $groupId = $eavSetup->addAttributeGroup(
            $entityTypeId,
            $attributeSetId,
            'License Data',
            30
        );

        $eavSetup->addAttributeToGroup($entityTypeId, $attributeSetId, $groupId, 'license_type', 10);
        $eavSetup->addAttributeToGroup($entityTypeId, $attributeSetId, $groupId, 'license_duration', 20);
        $eavSetup->addAttributeToGroup($entityTypeId, $attributeSetId, $groupId, 'download_limit', 30);

        $this->moduleDataSetup->getConnection()->endSetup();
    }

    /**
     * @return array<int, string>
     */
    public static function getDependencies(): array
    {
        return [];
    }

    /**
     * @return array<int, string>
     */
    public function getAliases(): array
    {
        return [];
    }
}

6. Assigning attribute sets: programmatically and via import

A new attribute set is of little use as long as no products reference it. Programmatically, the assignment happens through ProductRepositoryInterface: a product is loaded, setAttributeSetId() is set, and the product is persisted through save(). It is important to know that switching the attribute set on an existing product does not automatically delete attribute values, they simply stop being displayed in the new set as long as the attribute is not assigned there. For bulk changes across many products, instead of looping over save(), it is better to use the product mass actions in the admin grid or a dedicated batch process with explicit per product error handling, so that a single invalid record does not abort the entire run.

For CSV import, the assignment is handled through the attribute_set_code column, which expects the name of the attribute set as text, not the numeric ID. The import mechanism in Magento\CatalogImportExport\Model\Import\Product validates this value against the list of existing sets and aborts the affected row with a validation error if the name does not match exactly. This is exactly where consistent naming stops being a cosmetic detail: typos, or several sets with almost identical names such as "Clothing" and "Clothing " with a trailing space, lead to silent misassignments or aborted import rows whose root cause is not immediately obvious from the error log.


<?php

declare(strict_types=1);

namespace Mironsoft\AttributeSetGuard\Console;

use Magento\Catalog\Api\ProductRepositoryInterface;
use Magento\Eav\Api\AttributeSetRepositoryInterface;
use Magento\Eav\Api\Data\AttributeSetSearchResultsInterface;
use Magento\Framework\Api\SearchCriteriaBuilder;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;

/**
 * Reassigns a batch of products to a target attribute set by SKU list,
 * failing individually per SKU instead of aborting the whole run.
 */
final class ReassignAttributeSetCommand extends Command
{
    public function __construct(
        private readonly ProductRepositoryInterface $productRepository,
        private readonly AttributeSetRepositoryInterface $attributeSetRepository,
        private readonly SearchCriteriaBuilder $searchCriteriaBuilder,
    ) {
        parent::__construct('mironsoft:attribute-set:reassign');
    }

    /**
     * Configures the command name, arguments and description.
     *
     * @return void
     */
    protected function configure(): void
    {
        $this->setDescription('Reassigns products to a target attribute set by SKU');
        $this->addArgument('attribute-set-name', InputArgument::REQUIRED);
        $this->addArgument('skus', InputArgument::IS_ARRAY | InputArgument::REQUIRED);
    }

    /**
     * @param InputInterface $input Command input
     * @param OutputInterface $output Command output
     * @return int
     */
    protected function execute(InputInterface $input, OutputInterface $output): int
    {
        $setName = (string) $input->getArgument('attribute-set-name');
        $criteria = $this->searchCriteriaBuilder
            ->addFilter('attribute_set_name', $setName)
            ->create();

        /** @var AttributeSetSearchResultsInterface $result */
        $result = $this->attributeSetRepository->getList($criteria);
        $items = $result->getItems();
        if (count($items) === 0) {
            $output->writeln(sprintf('<error>Attribute set "%s" not found</error>', $setName));
            return Command::FAILURE;
        }

        $targetSetId = current($items)->getAttributeSetId();

        foreach ((array) $input->getArgument('skus') as $sku) {
            try {
                $product = $this->productRepository->get((string) $sku);
                $product->setAttributeSetId((int) $targetSetId);
                $this->productRepository->save($product);
                $output->writeln(sprintf('[OK] %s moved to set %s', $sku, $setName));
            } catch (\Throwable $exception) {
                $output->writeln(sprintf('[FAIL] %s: %s', $sku, $exception->getMessage()));
            }
        }

        return Command::SUCCESS;
    }
}

7. Governance process: permissions and naming conventions

Technical solutions like Data Patches only prevent sprawl if they are actually used. The second, equally important building block is a governance process: who on the team is even allowed to create a new attribute set? In most agency projects the implicit answer is "anyone with admin access", which in practice leads to exactly the sprawl described at the start. An effective approach restricts the native ACL right for attribute set management in the admin, via the user role management, to a small circle, typically backend development and one named responsible person on the client side, while editors and merchandisers keep editing products but cannot create new sets.

Equally important is a written naming convention as a mandatory practice, for example following the pattern product type, category, optional variant, so "Configurable - Clothing" or "Simple - Electronics - Accessories". Such a scheme makes the purpose of an attribute set immediately recognizable without anyone having to inspect its assigned attributes one by one, and it prevents the near identical name collisions described in the import section. In projects with several contributors, it has proven useful to not just document this convention but also enforce it technically through an observer or plugin pattern on the save action of the attribute set controller: a name that does not match the defined pattern is rejected with a clear error message instead of being silently saved. That way the convention stays effective even when the written documentation is forgotten.

8. Performance and indexing implications

A frequently underestimated consequence of too many attribute sets concerns admin performance, not primarily the frontend. The dropdown for selecting the attribute set on the "New Product" page renders noticeably slower with several hundred entries, because the list is built unfiltered and unsorted as a plain HTML select. More serious is the effect on internal configuration resolution: Magento\Eav\Model\Config::getEntityAttributes() is called per combination of entity type and attribute set, and the result is cached separately. The more distinct sets exist, the more individual cache entries need to be rebuilt after a cache flush, which shows up as a noticeable delay the first time every product page loads in the admin after a deploy.

For the catalog indexers themselves, the sheer number of attribute sets is a secondary factor, since the relevant indexers such as "Product EAV" work primarily per attribute, not per set. The real performance lever lies in the consequence of uncontrolled growth: when many sets contain nearly identical attributes in a slightly different combination, the number of unique attribute configurations that the system needs to hold and cache grows without any business benefit. The same applies to imports: validating against several hundred attribute sets per row does not take noticeably longer thanks to an indexed name lookup, but the error proneness from typos or mix ups rises proportionally to the number of similarly named sets.

9. Consolidating safely: unused attribute sets compared

Before an attribute set is deleted, it must be established beyond doubt that no product references it anymore. A simple check query groups the catalog_product_entity table by attribute_set_id and compares the result against the list of all sets from eav_attribute_set. Sets that show up with zero assigned products in this comparison are candidates for cleanup, but should additionally be checked against draft products, disabled products and multi store assignments before deletion, since a product still references an attribute set even while it has a "Disabled" status. Only after this double check should deletion happen through AttributeSetRepositoryInterface::deleteById(), since this method correctly resolves the referential integrity of the associated groups and attribute assignments, whereas a direct database intervention can leave orphaned rows in eav_attribute_group and eav_entity_attribute.

For a productive audit tool, it is worth building a dedicated console command that automates exactly this comparison and prints it as a table, complemented by a System.xml configuration through which a threshold for the minimum number of products per attribute set can be stored. That way, the audit run can be executed regularly via cron and automatically reports sets that fall below the configured threshold, instead of someone triggering the check manually and irregularly. The threshold itself is read through ScopeConfigInterface from a custom system.xml field, so the client or project lead can adjust it without a code change.


<?php

declare(strict_types=1);

namespace Mironsoft\AttributeSetGuard\Console;

use Magento\Eav\Api\AttributeSetRepositoryInterface;
use Magento\Framework\Api\SearchCriteriaBuilder;
use Magento\Framework\App\Config\ScopeConfigInterface;
use Magento\Framework\App\ResourceConnection;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Helper\Table;

/**
 * Audits all attribute sets and reports those below the configured
 * minimum product threshold, read from system.xml via ScopeConfigInterface.
 */
final class AuditAttributeSetsCommand extends Command
{
    private const XML_PATH_MIN_PRODUCTS = 'mironsoft_attributesetguard/general/min_products_threshold';

    public function __construct(
        private readonly AttributeSetRepositoryInterface $attributeSetRepository,
        private readonly SearchCriteriaBuilder $searchCriteriaBuilder,
        private readonly ResourceConnection $resourceConnection,
        private readonly ScopeConfigInterface $scopeConfig,
    ) {
        parent::__construct('mironsoft:attribute-set:audit');
    }

    /**
     * Configures the command name and description.
     *
     * @return void
     */
    protected function configure(): void
    {
        $this->setDescription('Lists attribute sets below the configured minimum product count');
    }

    /**
     * Compares eav_attribute_set against catalog_product_entity and prints
     * every set whose product count is below the configured threshold.
     *
     * @param InputInterface $input Command input
     * @param OutputInterface $output Command output
     * @return int
     */
    protected function execute(InputInterface $input, OutputInterface $output): int
    {
        $threshold = (int) $this->scopeConfig->getValue(self::XML_PATH_MIN_PRODUCTS);
        $connection = $this->resourceConnection->getConnection();

        $productCounts = $connection->fetchPairs(
            $connection->select()
                ->from($this->resourceConnection->getTableName('catalog_product_entity'), ['attribute_set_id'])
                ->columns(['product_count' => 'COUNT(*)'])
                ->group('attribute_set_id')
        );

        $criteria = $this->searchCriteriaBuilder->create();
        $rows = [];
        foreach ($this->attributeSetRepository->getList($criteria)->getItems() as $attributeSet) {
            $setId = (int) $attributeSet->getAttributeSetId();
            $count = (int) ($productCounts[$setId] ?? 0);
            if ($count < $threshold) {
                $rows[] = [$setId, $attributeSet->getAttributeSetName(), $count];
            }
        }

        $table = new Table($output);
        $table->setHeaders(['Set ID', 'Name', 'Products'])->setRows($rows);
        $table->render();

        return Command::SUCCESS;
    }
}
Task Unsafe / Risky Recommended pattern Benefit
Deleting a set DELETE FROM eav_attribute_set AttributeSetRepositoryInterface::deleteById() Referential integrity stays intact, no orphaned rows
Creating a new set Ad hoc in the admin UI, undocumented Data Patch with EavSetup, versioned Reproducible across every environment, reviewable in code
Naming "Set 2", "Test", "NEW final" Product Type - Category - Variant Purpose immediately recognizable, no import collisions
Cloning a set Rebuilding attributes by hand initFromSkeleton() from a base set Groups and sort_order are carried over correctly
Assignment during import attribute_set_id hardcoded attribute_set_code, validated upfront Import aborts in a controlled way instead of assigning the wrong set

The value of this table is not in individual tips, it is in consistency across the whole project. A team that uses the recommended pattern for half of its operations and falls back to the ad hoc route for the other half still ends up with the same sprawl, just more slowly. Only consistent application, supported by an audit tool that makes deviations visible, keeps the number of attribute sets permanently in line with actual business requirements.

Mironsoft

Magento 2 architecture, EAV governance and Hyva frontend

Attribute sets getting out of control?

We analyze existing attribute sets, consolidate unused structures and build Data Patches and audit tools that permanently prevent sprawl in your catalog.

Inventory audit

Analysis of all attribute sets, identification of unused and duplicate structures

Migration & consolidation

Safe consolidation through Data Patches instead of risky manual interventions

Governance setup

ACL rights, naming conventions and audit commands for lasting control

10. Summary

Attribute sets are not a technical side topic, they are a central building block of catalog architecture that quickly gets out of hand without planning. The key is to align attribute sets with actual differences between product types, instead of creating a new one for every small deviation. Attribute groups with a cleanly set sort_order solve most of the requirements for which a new set would reflexively be created. Where a new set is genuinely needed, its creation belongs in a versioned Data Patch with EavSetup, ideally based on a cloned skeleton set through initFromSkeleton.

Just as important as the technique is the organizational framework: clear permissions for who may create new attribute sets, a binding naming convention, and an audit tool that regularly makes unused sets visible before they accumulate unnoticed. Teams that think through both levels together, technical creation and organizational control, consistently prevent exactly the sprawl that becomes the norm in unplanned agency projects after just a few years.

Attribute sets in Magento 2, the essentials at a glance

Planning before creation

New set only for business significant attribute differences, otherwise an additional attribute group in the existing set is enough.

Programmatic creation

Data Patch with EavSetup and initFromSkeleton instead of an ad hoc admin click. Versioned, reproducible, reviewable in code.

Governance & naming convention

Restrict the ACL right to a few roles. Enforce a fixed naming convention such as Product Type - Category - Variant.

Performance & consolidation

Regular audit against catalog_product_entity, deletion exclusively through AttributeSetRepositoryInterface.

11. FAQ: Managing Attribute Sets in Magento 2

1What exactly is an attribute set in Magento 2?
A combination of attribute groups and assigned attributes for an entity type such as the product. It determines which fields are visible in the admin form.
2Why do attribute sets sprawl in agency projects?
Because any admin user can create their own sets without checking whether an existing set would have sufficed. Without governance, redundant sets accumulate over the years.
3How do I plan attribute sets sensibly?
First check whether an existing set covers the requirements. New set only for significantly different attributes, not for one single missing field.
4Difference between attribute groups and attribute sets?
A set bundles several groups. The group is the tab in the product editor, the set is the overarching assignment that a product references.
5When to clone instead of creating from scratch?
Almost always. initFromSkeleton carries over base attributes, groups and sort_order consistently. Creating from scratch only for deliberately minimal sets.
6How do I create sets via a Data Patch?
Implement DataPatchInterface, inject EavSetupFactory via the constructor, call addAttributeSet, addAttributeGroup and addAttributeToGroup inside apply().
7Assignment via import?
Through the attribute_set_code column with the exact name as text. The import validates against existing sets and aborts the row on deviations.
8Who may create new sets?
A small, clearly named circle, typically backend development. Restrict the ACL right for it through user roles.
9Do many sets affect performance?
Mostly admin performance through more cache entries per set combination. The indexers themselves work primarily per attribute.
10Remove an unused set safely?
First check via a query against catalog_product_entity, then delete exclusively through AttributeSetRepositoryInterface::deleteById(), never via a direct DELETE.

Mironsoft

Magento 2 architecture, EAV governance and Hyva frontend

Ready to bring order to your attribute sets?

From the initial inventory through Data Patches to the audit tool: we bring structure to grown Magento catalogs and prevent attribute sets from growing out of control again.

Analysis

Full inventory of all attribute sets and their actual usage

Implementation

Data Patches for consolidation, assignment and cleanup without data loss

Long term

Governance process and audit command for lastingly clean structures