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 viaEavSetupinInstallProductLoyaltyAttribute(chapter 19), initially global scope, switched to website scope viaUpdateProductLoyaltyAttributeScopestarting chapter 25. - Category -
loyalty_bonus_category(decimal, default 0),InstallCategoryLoyaltyAttribute(chapter 20), created directly with website scope. - Customer -
loyalty_points_balance(int) andloyalty_tier(varchar, dropdown via theLoyaltyTiersource model),InstallCustomerLoyaltyAttributes(chapter 21),loyalty_tierautomatically computed viaLoyaltyTierBackendsince chapter 26. - Company -
loyalty_tier_override(varchar, reusing the sameLoyaltyTiersource model), via an extension attribute (etc/extension_attributes.xml) plusAddLoyaltyTierOverridePlugin(chapter 22), becausecompanyisn't an EAV entity. - Sales -
loyalty_points_earnedonsales_orderandsales_order_item, created as a real column viaSalesSetup::addAttribute()inInstallSalesLoyaltyAttributes(chapter 23).
Three techniques, one decision
- 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/*_datetimetables. - 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). - A flat entity with no dedicated setup helper (company and most third-party or custom entities without EAV): a custom column via
db_schema.xmlon the foreign table plusextension_attributes.xmlplus a plugin, because the fixed interface (CompanyInterface) allows no custom getters/setters (chapter 22).
A checklist for every new attribute
- 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.
- 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. - Set
user_defined,system => false,visibleandrequiredcorrectly - a forgottensystem => falsemakes a customer attribute unexpectedly read-only or undeletable in the admin (chapter 21). - 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 theaddAttribute()call is enough. - 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.
- 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).
- Don't forget
bin/magento setup:upgradeand the matching reindex (catalog_product_attributefor product/category attributes,customer_gridfor customer attributes) - for sales attributes, additionally check whethersales_order_gridneeds separate syncing (chapter 23). - Wrap every admin label in
__(), otherwise a later i18n CSV translation (block 11) won't catch it (chapter 24). - Add the relevant foreign module to the
module.xmlsequence(Magento_Eav,Magento_Customer,Magento_Sales,Magento_Company,Magento_Catalog- whichever entity is affected). - Changing an attribute already in production use? Use
updateAttribute()instead of deleting and recreating it - existing values stay in place understore_id = 0as 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 likeaddAttribute()(chapter 25).- Entity type codes like
'order'/'order_item'inSalesSetupare string literals with no autocompletion - a typo only fails deep inside the framework (chapter 23). extension_attributes.xmlalone 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 usegetCategoryIds()(plural) for calculations (chapter 27).- A backend model must not type its
$objectparameter, becauseAbstractBackenditself 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.