Catalogue & product data
Hierarchical Category Tree
Generated from docs/plan/core/hierarchical-category-tree/PLAN.md in the AstroBaaS repository. Nothing described below is implemented — it is the written plan for it.
A nested category structure (Eyewear > Sunglasses > Polarized) that replaces the flat category list. Enables parent and child category rules (filter, sort, content), category-level permissions, and complex category navigation. Essential for shops with >3 category levels; without hierarchy, large catalogs become unusable.
The problem
My categories are nested 4 levels deep: Eyewear > Sunglasses > Polarized > Oversized. The flat category list doesn’t show parent-child relationships. I need different rules per level (sunglasses get ‘polarization’ filter, eyewear gets ‘lens type’ filter). I can’t model this with flat categories.
What it does
- Category hierarchy: parent_category_id allows unlimited nesting (but recommend max 5 levels)
- Category URLs: category path is hierarchical (/eyewear/sunglasses/polarized/oversized) and human-readable
- Category rules per level: ‘Sunglasses category: show size + color + price filters’, ‘Polarized category: show only polarized products’
- Category attributes: description, image, SEO title/meta, featured products per category, category-level discounts or badges
- Breadcrumb navigation: category page shows breadcrumb trail: Eyewear > Sunglasses > Polarized
- Product assignment: product can belong to multiple leaf categories (or just one, merchant chooses)
- Category filtering in admin: show category tree structure, drag-to-reorder, hide/unhide categories
- Category facets: category-specific facets (e.g., show lens color facet only on Sunglasses page)
- Bulk operations: move 50 products from old category to new category tree position
- Category permissions (optional, for multi-vendor shops): vendor can only edit products in assigned categories
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.
- Dynamic category generation (AI creates categories from products) — that is AI classification. Categories are merchant-curated.
- Category pre-orders or backorder status — that is fulfillment. Categories hold products; fulfillment owns status.
- Multi-language category names/descriptions — that is i18n module. Categories stored in one language.
- Auto-expanding/collapsing category tree (UX) — that is frontend design. Core stores hierarchy; frontend owns UX.
- Category-specific payment methods — that is payments module. Categories are catalog only.
Data model
New CATEGORY table with fields: id, name, parent_category_id (nullable), slug (unique per level), description, image_url, seo_title, seo_meta, position. Migration required: backfill categories with parent_category_id=NULL (root level), add slug field.
API
- POST /admin/categories (create category with optional parent_id)
- GET /admin/categories (return tree structure with parent-child relationships)
- PATCH /admin/categories/:id (update category metadata, reorder, reparent)
- DELETE /admin/categories/:id (delete category tree, warn if products assigned)
- GET /categories/:slug (get category and all products in category + child categories)
- POST /admin/products/:id/categories (assign product to category; can be multiple)
- PATCH /admin/categories/:id/products/bulk-move (move N products from old to new category)
Every route added here must also appear in src/pages/openapi.json.ts — a test
fails the build if it does not.
Admin
Category tree view showing parent-child structure. Create category modal with parent picker. Category detail page shows name, parent, slug, description, image, SEO fields, featured products, and rules. Drag-to-reorder categories at each level. Product detail shows categories assigned (multi-select picker). Bulk move: select products, pick target category, move all at once.
The seam — why this is core
Core owns category hierarchy, tree structure, and product assignment. Core does not own multi-language category names (that is i18n module) or category-specific rules (pricing, filters — those are rule modules). Core owns the interface; paid modules consume it.
Flat categories are a hard wall for any shop with >3 levels. This is a structural database change (tree paths, recursive queries) that enables complex category merchandising.
Dependencies
- Product model (core, existing)
- Faceted search (optional, for category facets)
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.
- Category tree: Eyewear (parent) > Sunglasses (child) > Polarized (grandchild) is created; each has own slug
- Category URL is hierarchical: /eyewear/sunglasses/polarized; breadcrumb shows path
- Product assigned to Polarized category; appears on Polarized page and all ancestor pages (Sunglasses, Eyewear)
- Category description and image are displayed on category landing page
- Reorder categories: Sunglasses moved below Prescription; tree updates and category URLs remain valid (redirects applied)
- Delete Polarized category: warning shows ‘150 products assigned to Polarized and child categories’. If delete confirmed, products are unassigned or moved per merchant choice.
- Bulk move: select 50 products, move from Sunglasses to Prescription; all 50 change category at once
- SEO: category landing page has title/meta tags from seo_title/seo_meta fields
Risks
If URL slugs change during reparenting, SEO links break. Mitigate: implement 301 redirects from old URLs to new URLs. If product assignment to deleted category is not handled, orphaned products occur. Mitigate: prompt merchant to move or unassign products before deletion. If category hierarchy is cyclic (A > B > C > A), infinite loops occur. Mitigate: validate on reparent to prevent cycles (use ancestor check).
Commercial context
| Suggested price | Free; core navigation structure |
| Rival anchor | Magento Open Source category hierarchy; 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.