Catalogue & product data
Category Management
Generated from docs/plan/core/category-management/PLAN.md in the AstroBaaS repository. Nothing described below is implemented — it is the written plan for it.
Core feature for organizing products into a hierarchical category system. Merchants can create parent/child categories, assign products to multiple categories, and control category visibility and ordering. This is foundational storefront infrastructure; advanced category filtering or faceted search belongs in a search plugin.
The problem
Merchants can’t organize products into categories; all 5,000 SKUs appear as a flat list.
What it does
- Create category: name, slug (auto-generated), optional parent category (hierarchical), description (optional), featured image (optional), is_active (boolean)
- Edit category: update name, description, parent, image, active status, reorder
- Delete category: soft delete (mark inactive), preserve products and historical data
- Restore category: reactivate a deleted category
- Category hierarchy: show parent-child relationships, support up to 3 levels deep (root → subcategory → sub-subcategory)
- Product assignment: assign products to multiple categories via admin or bulk edit
- Category-product count: show how many products in each category (including children)
- Reorder categories: drag-drop or up/down buttons to set display order
- Category page: GET /categories/:slug returns category with products, description, image
- Category list: GET /categories returns all active categories with hierarchy (tree structure or flat list)
- SEO fields: meta title, meta description, OG image per category for search/social sharing
- Category filtering: products can be filtered by category (GET /products?category=tops)
- Visibility control: hide category from storefront without deleting (is_active=false)
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 category assignment based on AI/tags — merchant assigns manually or via bulk edit
- Category-specific pricing or discounts — use promotions tier; core categories are organizational only
- Category-specific inventory rules — stock is per-product/variant; no category-level reservations
- Faceted navigation/filtering — that’s search plugin responsibility; core provides category list only
- Category-specific shipping or tax rules — those are global; no category overrides in core
Data model
New entity: category (id, name: string (max 255), slug: string (unique, max 128), parent_category_id: nullable foreign key, description: text, image_url: string (nullable), is_active: boolean, position: integer (for ordering), seo_title: string (nullable), seo_description: string (nullable), created_at, updated_at, deleted_at (nullable)). Link through product_categories join table (product_id, category_id, position).
API
- GET /categories — list all active categories (tree or flat)
- POST /categories — create category {name, parent_category_id?, description?, image?, is_active?}
- GET /categories/:id — retrieve category with product count and children
- PUT /categories/:id — update category (name, parent, description, image, active, position)
- DELETE /categories/:id — soft delete category
- PUT /categories/:id/restore — restore category
- POST /categories/:id/products — assign product to category
- DELETE /categories/:id/products/:product_id — remove product from category
- GET /products?category=:category_slug — filter products by 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 management panel: (1) Category tree view (hierarchical list with parent-child indentation), (2) Create/edit modal (name, parent selector, description WYSIWYG, image uploader, active toggle, position), (3) Product assignment (drag-drop or modal to select products for category), (4) Product count per category, (5) Reorder categories (drag-drop between parent levels), (6) SEO fields (title, description, OG image).
The seam — why this is core
Core owns the category schema, hierarchy, and CRUD API. Storefront (Next.js) owns category page rendering and filtering. Core provides category tree data; storefront decides how to display (breadcrumbs, sidebar, mega menu).
Core owns the interface + honest category organizer; product taxonomy is core infrastructure, not a per-country obligation or credential.
Dependencies
- product-management (core; products link to categories)
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.
- POST /categories with {name: ‘Tops’, is_active: true} creates category and returns {id: ‘cat_abc’, slug: ‘tops’, product_count: 0}.
- Creating a category with parent_category_id=‘cat_abc’ creates a child; GET /categories/cat_abc returns parent with children list.
- Assigning product ‘shirt_123’ to category ‘tops’ updates product_categories join table; GET /products?category=tops includes ‘shirt_123’.
- Soft-deleting a category (DELETE /categories/:id) sets deleted_at and is_active=false; products in that category are unaffected.
- GET /categories (no auth) returns only categories with is_active=true; deleted categories are hidden.
- Changing category.parent_category_id moves it to a different parent; children follow (hierarchy is preserved).
- A category with 150 products shows product_count=150 (includes children categories if hierarchical count is used).
- Exporting categories as CSV includes id, name, parent_name, product_count, active status.
Risks
If a category is deleted while products still reference it, GET /products?category=deleted_cat may return stale results. If hierarchy depth is not enforced, a deeply nested category tree (10+ levels) can confuse the storefront. If category slugs are reused after deletion, URL conflicts occur.
Commercial context
| Suggested price | free (core) |
| Rival anchor | Shopify: included; Magento: included |
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.