Multi-Store Product Data Model for Regional Attributes
A retailer sells in 5 countries. Products need: a global SKU and name, per-country localized descriptions (5 languages), per-country warranty text, and a per-country is_sellable flag. They also need a global launch_date and a per-country launch_date_override that is usually empty. Current implementation: every field is a global attribute, so descriptions are duplicated per store view and the catalog is 3x larger than necessary. Editors complain they re-enter warranty text for every store view. Reporting needs to filter products by country sellability. The catalog has 250k products.
1. Classify each field by scope (global/website/store) and justify. Identify which fields must NOT be store-scoped and why.
Read the worked answer
Scope assignment:
sku,name(if truly global),launch_date: global - one value, no duplication.skumust be global for identity.- Localized descriptions (5 languages): store view - text differs by language, which maps to store view. This is the legitimate store-scope case.
warranty_text,is_sellable,launch_date_override: website - these vary by country, and country maps to website (or store group), not store view. Scoping them to store view forces re-entry per language within a country.
Why not store-scope warranty/sellability: a country with 2 languages would duplicate the same warranty text twice, and is_sellable would need to be consistent across languages - store scope invites drift. Website scope gives one authoritative value per country, editable once.
Caveat: if a country has multiple websites (e.g., B2B vs B2C in same country), website scope splits them; then use store-group scope or a country attribute. Confirm the website↔country mapping before finalizing.
2. Design the schema to eliminate the 3x bloat and support reporting on country sellability. Address the `launch_date_override` pattern.
Read the worked answer
Use native attribute scoping rather than one global column:
- Global fields →
catalog_product_entityor a global EAV table (catalog_product_entity_varcharwithstore_id=0). - Store-scoped descriptions →
catalog_product_entity_textrows withstore_idper language. - Website-scoped warranty/sellability/override →
catalog_product_entity_*rows withstore_id = 0but a website-scoped attribute so Commerce writes per-website values intocatalog_product_entity_*keyed by website via the scoped indexer. (Native EAV store_id is store-view; for website scope you rely on attributescope=websiteand the flat/index tables carryingstore_idper website's default store.)
For the override pattern: do not store a duplicate launch_date_override per country when usually empty. Instead keep global launch_date and a website-scoped launch_date_override; at read time resolve COALESCE(override, global). This keeps nulls sparse.
For reporting: build a flat index table catalog_product_flat_* or a custom reporting table (product_country_sellability) populated by an indexer, with columns (product_id, website_id, is_sellable, effective_launch_date). Report against that, not EAV joins - 250k products × 5 countries is fine in a flat table, painful in EAV.
3. Editors still re-enter warranty text per store view. What is wrong and how do you correct it without a data migration disaster?
Read the worked answer
If warranty is still duplicated per store view, the attribute is still scope=store (or the admin UI is configured that way). Changing an attribute's scope in Commerce drops its existing data - scope changes are destructive. So you cannot simply flip the scope.
Safe path:
- Create a new website-scoped attribute
warranty_text_ws. - Write a migration that, for each product, picks the canonical value per website (e.g., the default store view's value) and populates the new attribute per website.
- Update templates, GraphQL/REST schema, and any code reading the old attribute.
- Verify, then deprecate/remove the old attribute.
Do this in a maintenance window with a dry run on a catalog copy. Back up catalog_product_entity_text first. Never alter scope on a live attribute with 250k rows.