AstroBaaS

Catalogue & product data

Variable Products (First-Class)

Free — GPL coresize Lplanned, not built

Generated from docs/plan/core/variable-products/PLAN.md in the AstroBaaS repository. Nothing described below is implemented — it is the written plan for it.

A first-class product model where variants (sizes, colors) are stored as independent products with their own SKU, inventory count, URL, and pricing. Currently variants are embedded sub-records; this elevates them to top-level products so each variant can have its own page, stock tracking, and analytics. Essential for ecommerce: when size L sells out, size M should remain discoverable and purchasable.

The problem

When I sell out of size L, the whole product disappears from search results because I don’t have separate SKUs per size. I need each size to be a standalone product with its own stock, URL, and availability — but they should show as one product to customers when browsing, and link together on the detail page.

What it does

  • Product.variant_type field: ‘none’ (simple product), ‘configurable’ (has variants), or ‘child’ (is a variant of a parent)
  • Configurable products have a ‘variant_attributes’ array: which attributes define variants (e.g., size, color)
  • Child products (variants) have parent_product_id, inherit brand/category/description from parent, have own SKU/price/inventory
  • Inventory is tracked per variant (child product): when size L is out of stock, size M is still buyable
  • Product detail page shows variant selector (size picker, color picker) and updates price/inventory/SKU as user selects variant
  • URL structure: /products/parent-id (shows parent + variant picker) or /products/variant-sku (SEO-friendly variant-specific URL)
  • Admin product form: ‘Create Variants’ button generates child products from attribute combinations (5 sizes × 3 colors = 15 variants auto-created)
  • Bulk variant operations: bulk-assign price premium per size (size XL = price + €5), bulk-update inventory for all sizes
  • Variant can be hidden individually (e.g., hide L even though parent is live) without hiding parent product
  • Analytics: report variant popularity (which sizes sell best), variant-level revenue attribution

What it deliberately does NOT do

Each boundary carries its reason. A boundary without a reason gets crossed by the next person who reads this.

  • Variant-specific images or swatches — media module owns image management; variants reference images by URL, do not store images.
  • Automatic price/inventory sync from supplier APIs — that is inventory sync module. Variants store SKU; sync module populates inventory.
  • Rules for auto-generating variants — that is data enrichment module. Admin manually creates variants or uses bulk import.
  • Variant grouping beyond one parent — this is one-level hierarchy only (parent ← variants). Multi-level product hierarchies are a separate feature.
  • Variant pre-orders or backorder logic — that is fulfillment module. Variants store inventory; fulfillment owns backorder state.
  • Variant-specific tax or shipping rules — that is tax/shipping module. Variants are products; tax/shipping module applies standard rules per product type.

Data model

New field on PRODUCT: variant_type (enum: ‘none’ | ‘configurable’ | ‘child’), parent_product_id (nullable), variant_attributes (JSON array). New PRODUCT_VARIANT_COMBINATION table (parent_id, child_id, attribute_values JSON, display_order). Migration required: backfill variant_type=‘none’ for all existing products.

API

  • POST /admin/products/:id/variants/create (generate variants from attribute combinations)
  • GET /admin/products/:id/variants (list variants of a parent, with inventory + price)
  • PATCH /admin/products/:id/variants/:variantId (update variant price, SKU, inventory, hide status)
  • POST /admin/products/:id/variants/bulk-update (apply price or inventory delta to all variants)
  • GET /products/:id (variant_type, parent info if child, variant_attributes)
  • GET /products/:id/variants (if configurable: list available variants with prices and inventory)

Every route added here must also appear in src/pages/openapi.json.ts — a test fails the build if it does not.

Admin

Product detail shows ‘Variants’ section. If product is configurable, ‘Generate Variants’ button creates variants from selected attributes (5 sizes × 3 colors = 15). Variants list shows variant ID, selected attribute values (size=L, color=Red), SKU, price, inventory, and hide toggle. Bulk update dialog: apply price offset (size XL = +€5) or inventory adjustment (all variants -10 units) to all at once.

The seam — why this is core

Core owns variant schema, parent-child relationships, inventory tracking per variant, and URL routing. Core does not own automatic variant generation from supplier data (that is inventory module) or variant images/swatches (that is media module). Core owns the interface; paid modules consume it.

Currently variants are embedded sub-records. Magento makes them first-class products with own SKU, inventory, URLs, and reporting. This is table stakes for modern ecommerce.

Dependencies

  • flexible-product-attributes (must exist first; variants use attributes)
  • Inventory management (core, existing)
  • Product routing/URLs (core, existing)

Acceptance checks

Each of these must be able to fail. Before claiming this is done, break the code deliberately and watch each one go red.

  • Configurable product ‘T-Shirt’ with variant_attributes [size, color] is created; ‘Generate Variants’ creates 3×2=6 variants auto-generated
  • Each variant has own SKU (e.g., TSH-S-BLU, TSH-M-BLU, TSH-L-BLU) assigned automatically or manually
  • Variant size L is out of stock (inventory_count=0); size M is in stock (inventory_count=50). Product detail page shows ‘Size L Sold Out’ but Size M is selectable.
  • Variant-specific URL /products/TSH-L-RED redirects to parent /products/t-shirt with size=L&color=RED pre-selected
  • Bulk update ‘all variants of T-Shirt get -5 inventory’ reduces all variants by 5 simultaneously
  • Variant is hidden (hide=true) but parent product is still visible; variant doesn’t appear in variant picker
  • Analytics report shows variant popularity: ‘50 units of size L, 100 units of size M, 25 units of size XL sold this month’
  • Child product (variant) inherits parent’s description, category, brand; child can override price and inventory only

Risks

If variant inventory is not tracked separately, overselling occurs (L and M share inventory pool). Mitigate: enforce inventory per variant, not parent. If parent is deleted, orphaned variants remain (dangling references). Mitigate: cascade delete or auto-reassign to ‘discontinued’ parent. If variant URLs are not SEO-friendly, ranking is lost. Mitigate: use variant SKU in URL slug, ensure variant URLs are canonical. If variant generation is not atomic, half-created variants corrupt data. Mitigate: transaction wrapper, rollback on error.

Commercial context

Suggested priceFree; core storefront feature
Rival anchorMagento Open Source configurable products; Shopify variants

The anchor is what the nearest equivalent charges on Shopify or Magento today. It is context for a pricing decision, not the decision.


Generated from the commerce plan. See docs/COMMERCE-PLAN.md for the full catalogue and ../../AI-GUIDE.md for how to work on this repository.