AstroBaaS

Catalogue & product data

Category Management

Free — GPL coresize Mplanned, not built

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 pricefree (core)
Rival anchorShopify: 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.