Catalogue & product data
Grouped Products
Generated from docs/plan/core/grouped-products/PLAN.md in the AstroBaaS repository. Nothing described below is implemented — it is the written plan for it.
A lightweight product type where a merchant groups 2–5 related products (e.g., tripod, camera, lens, battery, bag) and displays them together on a ‘group page’ with each product buyable independently or together. Unlike bundles, group products are not sold as a composite; each keeps its own SKU and price. Group pages solve the ‘frequently bought together’ problem without adding complexity.
The problem
I want to show a camera, tripod, lens, battery, and bag on one page with a headline ‘Everything You Need to Start Shooting’ — each product is buyable alone or I can add all to cart with one click. Right now I use related products, but the UX is weak. I need a dedicated ‘group’ page.
What it does
- Grouped product type: select ‘Grouped’ when creating product
- Group composition: add up to 10 related products with order (tripod first, then camera, etc.)
- Group page displays: each product with image, name, price, inventory status, ‘Add to Cart’ button
- Bulk add-to-cart: ‘Add All to Cart’ button adds all products in the group to cart in one action
- Optional group items: merchant can mark items as ‘featured’ (shown first) or ‘optional add-ons’ (shown last)
- Group pricing: no group discount (each product keeps its price) — unlike bundles
- Group SEO: group has own URL (/products/camera-kit) and meta tags, not a redirect to first product
- Group analytics: track group page views, which products in group were clicked, which were added to cart
- Group inventory: products within group show live inventory; ‘Add All’ respects inventory for each product
- Group customization: merchant can add custom text block between products (‘Choose your battery capacity below’)
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.
- Automatic grouping (AI suggests related products) — that is recommendation module. Groups are merchant-curated.
- Group-level discounts or bundle pricing — that is promotions module. Groups show individual prices only.
- Frequently-bought-together dynamic grouping — that is analytics/recommendation module. Groups are static.
- Group inventory combining (treat 5 products as one stock pool) — that is bundler, not grouper. Groups are independent.
- Cross-category grouping with fallback (if camera out of stock, show alternate camera in group) — that is fulfillment substitution.
Data model
New field on PRODUCT: product_type (enum: ‘simple’ | ‘configurable’ | ‘bundle’ | ‘grouped’). New GROUPED_PRODUCT_ITEMS table (group_id, child_product_id, position, is_featured). Migration required: update existing products to product_type=‘simple’.
API
- POST /admin/products (create grouped with child products)
- PATCH /admin/products/:id (update group composition)
- GET /products/:id (response includes product_type=‘grouped’, child_products array)
- POST /cart (add grouped items: {group_product_id, included_item_ids: [product_1, product_2, …]})
Every route added here must also appear in src/pages/openapi.json.ts — a test
fails the build if it does not.
Admin
Product create form includes product_type selector. If grouped: composition section with ‘Add Product’ button. Child product list shows order, featured toggle, remove button, and each product’s current price and inventory. Preview button shows how group page will look. No group price field (unlike bundle).
The seam — why this is core
Core owns grouped product schema, page rendering, and cart logic. Core does not own automatic grouping (that is ML/recommendation module) or group-level promotions (that is promotions module). Core owns the interface; paid modules own consumption logic.
Less common than bundles but covers bundled merchandising. Simpler to implement (lightweight wrapper over related products). Completes the product-type system.
Dependencies
- Product model (core, existing)
- Cart system (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.
- Grouped product ‘Camera Kit’ is created with products: tripod, camera, lens, battery (4 products)
- Group page shows 4 products with own images, names, prices (€50, €800, €200, €30)
- Inventory shown per product: camera 2 in stock, tripod 0 in stock; group page shows this per item
- ‘Add All to Cart’ adds all 4 products; if tripod is out of stock, error shown and add-to-cart fails
- Bulk add respects per-product qty field (if merchant set ‘max qty per customer = 1 for camera’, bulk add adds 1 camera)
- Group page has own SEO title and meta description; canonical URL is /products/camera-kit
- Analytics show group page views (200 views), click-through per product (camera 150 clicks), add-to-cart per product (camera 50 adds)
- Group is removed from search if marked ‘hidden’; children remain visible individually
Risks
If ‘Add All’ button does not respect inventory per product, customer sees error after clicking (bad UX). Mitigate: validate inventory before allowing add-to-cart, show warning if any item is out of stock. If group page is not crawlable (SEO-hidden), group traffic is lost. Mitigate: group has own canonical URL and meta tags, is indexed by search. If a child product is deleted, group becomes orphaned and broken. Mitigate: warn merchant when deleting product that is in groups, and offer to remove from group or soft-delete.
Commercial context
| Suggested price | Free; completes product-type system |
| Rival anchor | Magento Open Source grouped products; free |
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.