Magento 2 Experten — Hyvä Theme, Tailwind CSS & SEO aus einer Hand ›

Attribute Block Summary: A Checklist for Your Own Attributes

Attribute Block Summary: A Checklist for Your Own Attributes

~6 Min. Lesezeit Zuletzt aktualisiert am August 9, 2026

Nine chapters, five attributes, three fundamentally different storage techniques, one backend model, and one resolver service for consistency across attribute boundaries - this chapter sums up block 3 (chapters 19-27) as a reference, before block 4 starts the next topic with observers and cron in chapter 29. Meant to be returned to, not just read once.

The five attributes in review

  • Product - loyalty_points_multiplier (decimal, default 1.0000), created via EavSetup in InstallProductLoyaltyAttribute (chapter 19), initially global scope, switched to website scope via UpdateProductLoyaltyAttributeScope starting chapter 25.
  • Category - loyalty_bonus_category (decimal, default 0), InstallCategoryLoyaltyAttribute (chapter 20), created directly with website scope.
  • Customer - loyalty_points_balance (int) and loyalty_tier (varchar, dropdown via the LoyaltyTier source model), InstallCustomerLoyaltyAttributes (chapter 21), loyalty_tier automatically computed via LoyaltyTierBackend since chapter 26.
  • Company - loyalty_tier_override (varchar, reusing the same LoyaltyTier source model), via an extension attribute (etc/extension_attributes.xml) plus AddLoyaltyTierOverridePlugin (chapter 22), because company isn't an EAV entity.
  • Sales - loyalty_points_earned on sales_order and sales_order_item, created as a real column via SalesSetup::addAttribute() in InstallSalesLoyaltyAttributes (chapter 23).

Three techniques, one decision

  1. A real EAV entity (product, category, customer, the custom reward entity from block 2): EavSetup::addAttribute()/updateAttribute(), values land in the *_varchar/*_int/*_decimal/*_text/*_datetime tables.
  2. A flat sales entity with its own setup helper (order, order item, invoice, credit memo): SalesSetup::addAttribute() - looks like EAV, but creates a real column (chapter 23).
  3. A flat entity with no dedicated setup helper (company and most third-party or custom entities without EAV): a custom column via db_schema.xml on the foreign table plus extension_attributes.xml plus a plugin, because the fixed interface (CompanyInterface) allows no custom getters/setters (chapter 22).

A checklist for every new attribute

  1. Is the target entity a real EAV entity, a Sales entity, or flat with no dedicated setup helper? Immediately determines which of the three techniques above applies.
  2. Choose the scope deliberately (SCOPE_GLOBAL/SCOPE_WEBSITE/SCOPE_STORE), don't just accept the EAV default (chapter 25) - this question doesn't apply at all to flat entities.
  3. Set user_defined, system => false, visible and required correctly - a forgotten system => false makes a customer attribute unexpectedly read-only or undeletable in the admin (chapter 21).
  4. Does the attribute need a dropdown? Place a reusable source model under an entity-neutral namespace (chapter 24), otherwise the short 'option' array form directly in the addAttribute() call is enough.
  5. Does the value need to be automatically calculated or validated on save? Register a backend model - but keep the contravariance trap for untyped parameters from chapter 26 in mind.
  6. Do several attributes, or several values of the same attribute (multiple categories!), feed into a calculation together? Document the resolution rule in its own, named resolver service instead of scattering it across observers or controllers (chapter 27).
  7. Don't forget bin/magento setup:upgrade and the matching reindex (catalog_product_attribute for product/category attributes, customer_grid for customer attributes) - for sales attributes, additionally check whether sales_order_grid needs separate syncing (chapter 23).
  8. Wrap every admin label in __(), otherwise a later i18n CSV translation (block 11) won't catch it (chapter 24).
  9. Add the relevant foreign module to the module.xml sequence (Magento_Eav, Magento_Customer, Magento_Sales, Magento_Company, Magento_Catalog - whichever entity is affected).
  10. Changing an attribute already in production use? Use updateAttribute() instead of deleting and recreating it - existing values stay in place under store_id = 0 as a fallback (chapters 25, 26).

Common pitfalls at a glance

  • addFieldToFilter() with an int value always as ['eq' => $value] - a CLAUDE.md project convention that applies equally to every collection on these attributes.
  • updateAttribute() expects the new value directly as its fourth parameter, not an associative array like addAttribute() (chapter 25).
  • Entity type codes like 'order'/'order_item' in SalesSetup are string literals with no autocompletion - a typo only fails deep inside the framework (chapter 23).
  • extension_attributes.xml alone doesn't automatically populate a field - without a plugin the value stays empty (chapter 22).
  • $product->getCategoryId() (singular) is request context, not a stored value - always use getCategoryIds() (plural) for calculations (chapter 27).
  • A backend model must not type its $object parameter, because AbstractBackend itself declares no type - PHP forbids narrowing a parameter type afterward (chapter 26).

Tipp: This checklist is worth bookmarking for any future module of your own that adds attributes to existing entities - regardless of whether it's a loyalty program or a completely different feature. The technique decision (EAV vs. sales setup vs. extension-attribute-plus-plugin) is the most important first step, because it determines everything that follows.

Block 4 begins in chapter 29 with the event system: exactly there - in the AwardPointsOnOrderPlaced observer from chapter 30 - PointsCalculator, CategoryBonusResolver, and the automatically updated attributes from this block all come together for the first time in a single flow: actually awarding points when an order is placed.