Magento’s product data model is famously flexible: merchants can add attributes - colour, material, care instructions - without touching the database schema. The mechanism behind that flexibility is EAV (entity-attribute-value), and understanding it explains both Magento’s power and several of its performance traps.
How EAV Stores Data
A traditional table stores one row per product with a column per field. EAV instead splits data across four table families:
- Entity:
catalog_product_entity- just the ID, SKU, type and timestamps - Attribute:
eav_attribute- the definition of each attribute (code, type, input) - Value tables: one per data type -
catalog_product_entity_varchar,_int,_text,_decimal,_datetime- each row storing (entity, attribute, value)
A product with 80 attributes is therefore 80+ rows scattered across five value tables. Reading a product means joining them all; the flat catalog index exists precisely to hide that cost from the storefront.
Adding an Attribute Programmatically
Admin-added attributes are fine for merchants, but modules should define attributes in code so they deploy with the project. Use a data patch:
namespace Acme\Catalog\Setup\Patch\Data;
use Magento\Eav\Setup\EavSetupFactory;
use Magento\Framework\Setup\ModuleDataSetupInterface;
use Magento\Framework\Setup\Patch\DataPatchInterface;
class AddCareInstructions implements DataPatchInterface
{
public function __construct(
private ModuleDataSetupInterface $setup,
private EavSetupFactory $eavSetupFactory
) {}
public function apply(): void
{
$eavSetup = $this->eavSetupFactory->create(['setup' => $this->setup]);
$eavSetup->addAttribute(
\Magento\Catalog\Model\Product::ENTITY,
'care_instructions',
[
'type' => 'text',
'label' => 'Care Instructions',
'input' => 'textarea',
'required' => false,
'visible_on_front' => true,
'used_in_product_listing' => true,
'user_defined' => true,
]
);
}
public static function getDependencies(): array { return []; }
public function getAliases(): array { return []; }
}
The flags matter: used_in_product_listing puts the value into the flat index and collection queries; visible_on_front controls display; user_defined means uninstall scripts leave it alone.
EAV vs Flat Tables for Custom Data
EAV is right when the data is genuinely merchant-extensible: product attributes, customer attributes, category fields. It is wrong for structured module data. A custom module storing, say, warehouse locations should create its own declarative-schema table with real columns and indexes - faster, queryable with SQL, and far easier to maintain. The anti-pattern we see in audits is modules stuffing structured relational data into EAV because “that’s what Magento does”.
Performance Notes
- Every attribute joined into a listing costs query time; keep
used_in_product_listingto attributes the grid actually shows - Hundreds of rarely-used attributes bloat the flat index and slow reindexing - prune ruthlessly
- Swatch and filterable attributes multiply layered-navigation work; only mark attributes filterable when customers genuinely filter by them
EAV is a deliberate trade: flexibility for merchants in exchange for complexity in the engine. Use it for what it is for, put everything else in real tables, and keep the attribute set lean - your reindex times will thank you.