Skip to content

PRD: Product options & variant generation

ModuleProduct (CORE-05)PRD IDPRD-OPT-002
StatusShippedOwnerProduct squad
Date2026-06-15Versionv1.0
Packages@nx/commerce · @nx/core · @nx/searchURDOPT · VAR

TL;DR

Turns a product's option axes into the variants a merchant actually sells, in one atomic create. The owner declares the axes (size, ice level, colour) and the exact variants to generate - each naming its own choice per axis - and the system creates the options, their values, every variant, its bindings, its identifiers, and its inventory attributes together. Generation is explicit, never a full cartesian blow-up: only the declared variants exist. One variant is always the default - the one the owner flags, or the first declared otherwise - and each variant's choices are pushed into search so it can be found and filtered by its options.

1. Context & Problem

The option binding model (PRD-OPT-001) defines what a variant's combination is and keeps it valid and unique. It does not define how a product and its full set of option-bearing variants come into existence in a single owner action.

Without a generation path, an owner would have to create a bare product, then add each variant, then attach options one binding at a time - several round-trips, each a chance to leave the catalogue half-built: a product with axes but no variants, a variant with no default, a barcode collision discovered only on the third call. Merchants also expect to type the variants they sell, not have the system multiply every axis into a matrix of phantom rows they must then prune.

This increment closes that gap: a single create request carries the axes and the declared variants, and the system materializes the whole product - options, values, variants, bindings, identifiers, inventory attributes, and one default - atomically, then makes every variant findable by its options in search.

2. Goals & Non-Goals

Goals

  • One atomic create that takes option axes + declared variants and generates the entire product graph.
  • Explicit generation only - the system creates exactly the declared variants, never the full option matrix.
  • Always resolve exactly one default variant - the flagged one, otherwise the first declared.
  • Carry each variant's SKU / barcode, with in-request and per-merchant barcode uniqueness.
  • Generate inventory attributes only for stockable variant types; non-stockable types and combos are created untracked.
  • Denormalize each variant's option choices into search so variants are findable and filterable by their options, kept fresh as axes/values/bindings change.

Non-Goals

  • Automatic cartesian expansion of all option combinations - covered as a deliberate non-goal in PRD-OPT-001; owners declare only what they sell.
  • The binding-model rules themselves (validity, one-per-axis, combination uniqueness, strict-options) - specified in PRD-OPT-001.
  • Pricing and stock figures - prices live in fares (FAR), stock lives in Inventory.
  • Bulk / CSV catalogue import (URD-PRD-019) - a separate increment.

3. Success Metrics

MetricTarget / signal
AtomicityA failed sub-step (bad option, duplicate barcode, two defaults) leaves nothing persisted
No phantom variantsVariant count equals the number declared - never the size of the option matrix
Default integrityEvery generated product has exactly one default variant
Barcode integrityNo two variants share a barcode within a merchant; in-request duplicates are refused
SearchabilityA generated variant is findable and filterable by each of its option values

4. Personas & Use Cases

PersonaGoal in this feature
Owner / ManagerCreate a product and all its real variants in one step, choosing the default
CashierFind and pick a generated variant by its options at the point of sale
Channel integrationLocate the right variant by filtering on its option facets in search

Core scenario: an owner creates a milk tea with axes Size (S/M/L) and Ice (100/50/0) and declares the five variants actually sold, flagging "M, 100% ice" as default. The system generates the axes, values, five variants, their bindings, identifiers, and inventory attributes in one atomic step; the flagged variant becomes default; each variant is immediately findable in search by size and ice. Declaring two defaults, or two variants with the same barcode, is refused before anything is saved.

5. User Stories

  • As an owner, I declare a product's axes and the exact variants I sell in one request, so the catalogue is complete the moment it is created.
  • As an owner, I want only the variants I name to exist, so I never have to delete combinations I don't sell.
  • As an owner, I flag one variant as default (or let the first stand), so the POS always has a sensible pick.
  • As an owner, I want a duplicated barcode caught before anything saves, so I never ship a clashing code.
  • As an owner, I change the default later without rebuilding the product, so the menu can evolve.
  • As a cashier, I find a generated variant by its size and ice, so ordering is a few taps.

6. Functional Requirements

#RequirementURD ref
FR-1Create takes both the option axes and the exact variants to generate, each declaring its own choice per axisURD-OPT-011
FR-2Only the declared variants are generated - no automatic full-matrix expansionURD-OPT-012
FR-3Axes, values, and every declared variant are generated together with the product in one atomic stepURD-OPT-013
FR-4Exactly one variant is default: the explicitly flagged one, else the first declared is promotedURD-OPT-014
FR-5More than one explicit default in the same request is refusedURD-OPT-015
FR-6The default can be changed after creation via a dedicated promote actionURD-OPT-016
FR-7Each generated variant may carry its own SKU / barcode; a barcode duplicated in-request or already used in the merchant is refusedURD-OPT-017 · URD-PID-001
FR-8Inventory attributes are generated only for stockable variant types; combos and non-stockable types are untrackedURD-OPT-018 · URD-VAR-006
FR-9Generated variants and their option choices are denormalized into search and kept fresh as axes/values/bindings changeURD-OPT-019
FR-10Option axes and values are normalized so the same choice written differently maps to one valueURD-OPT-006

Full requirement text and acceptance criteria live in the Product URD - OPT. This PRD references them rather than restating them. The binding-model requirements (URD-OPT-001..010) are specified in PRD-OPT-001.

7. Non-Functional Requirements

AreaRequirement
AtomicityThe whole product graph - options, values, variants, bindings, identifiers, inventory attributes, default - is one all-or-nothing transaction
Tenancy & authzAll operations scoped per merchant (x-merchant-id) and gated by product permissions
NormalizationOption keys and values are normalized (case-insensitive) so equivalent choices collapse to one
Identifier integrityBarcodes are checked for in-request duplicates and existing merchant conflicts before persistence
Eventual search consistencyDenormalized option facets are refreshed by a change-data cascade after commit, not in the create's critical path
i18nAxis names are multilingual ({ en, vi }); a value's display name falls back to its raw value when unset

8. UX & Flows

The create surface lets the owner define the axes and their ordered values, add each variant they sell with its choice per axis and an optional SKU / barcode, and mark one as default. Generated variants then appear in product search filterable by their option facets.

9. Data & Domain

EntityRole in generation
ProductOptionAn option axis the product varies by, generated from a declared axis (name, key, order)
ProductOptionValueOne ordered choice on an axis, generated from the axis's declared values
ProductVariantA generated sellable unit; exactly one per product is the default
ProductVariantOptionOne binding of a variant to a single (axis, value) pair - a variant's set of bindings is its combination

Conceptual only - full schema and invariants live in the commerce domain model. Relations are soft references; integrity is enforced in the generation service, not by database constraints.

10. Dependencies & Assumptions

Depends on

  • Option binding model (PRD-OPT-001, URD-OPT-001..010) - generation produces the bindings this model validates.
  • Variants & aggregate create (VAR, PRD-PRD-001) - variants and their fare set / identifiers are created through the variant aggregate.
  • Identifiers (PID) - SYSTEM / SLUG auto-generated; SKU / BARCODE per variant.
  • Inventory (Inventory) - stockable variants seed inventory items downstream.
  • @nx/search - denormalizes each variant's options and facets for lookup.

Assumptions

  • The owner declares the variants they actually sell; the system does not infer the full matrix.
  • A merchant's strict-options setting (from PRD-OPT-001) is read during generation and governs whether option-free variants are allowed.

11. Risks & Open Questions

Risk / questionMitigation / status
Partial product graph on failure (axes but no variants, no default)Whole graph is one atomic transaction - any failure rolls back fully
Two variants flagged defaultRefused before persistence with a clear, specific reason
Barcode clash discovered lateTwo-stage check - in-request duplicates, then existing merchant conflicts - before anything saves
Search shows stale option facetsFacets refreshed by a change-data cascade on axis, value, and binding changes
Owner expects auto-generated matrixDeliberate non-goal (PRD-OPT-001); only declared variants are generated

12. Release Plan & Launch Criteria

AspectPlan
PhaseP2 - OPT in the URD feature catalog, alongside VAR
RolloutAll merchants; no feature flag
MigrationNone - generation runs on the existing product aggregate-create path
Launch criteriaCreate generates exactly the declared variants atomically; one default is always resolved; two defaults and duplicate barcodes are refused; inventory attributes appear only for stockable types; generated variants are filterable by their option facets in search
MonitoringCreate error rate by reason (duplicate default, barcode conflict), search-facet freshness lag

13. FAQ

Does the system generate every size × ice combination for me? No - generation is explicit. You declare the variants you sell and exactly those are created; there are no phantom rows to prune. (See PRD-OPT-001 for the reasoning.)

What if I don't flag a default? The first variant you declare is promoted to default automatically, so a product always has one.

Can I flag two defaults? No - a request with more than one explicit default is refused before anything is saved. Change the default later with the dedicated promote action.

Do all variant types get inventory tracking? No - only stockable types generate inventory attributes. Combos and non-stockable types (e.g. services) are created untracked.

When can I search a new variant by its options? Each variant's option facets are denormalized into search after creation and kept current by a cascade whenever an axis, value, or binding changes.

References

Proprietary and Confidential. Unauthorized copying, distribution, or use of this software is strictly prohibited.