Categories / Taxonomy Audit¶
Run date: 2026-04-17. Source of truth: current main at commit d1a5411.
Findings¶
- No
categoriestable exists. Product classification is driven entirely byproducts.type, a VARCHAR column with a CHECK constraint pinned to three values:digital,membership,online_class(constraint defined in migration010_add_product_type_fields.sql). - Backend treats
typeas a type-of-product, not a category. The service layer switches onProductTypeDigital / ProductTypeMembership / ProductTypeOnlineClassfor validation and per-type field handling (backend/internal/services/product.goand friends). This is a "fulfillment kind" dimension, not a subject/topic taxonomy. - Frontend has a hard-coded category chip filter that is already stale.
frontend/src/pages/main/products/product-list.tsxandfrontend/src/pages/dashboard/selling/my-products.tsxboth ship a chip list with['all','digital','membership','course']. Thecoursechip matches no products because backend usesonline_class, notcourse. The filter is therefore broken for anything other thanall. - There is no subject/topic taxonomy at all. Nothing maps to Whop categories like "Trading", "Ecom", "Fitness", "Creator Tools". The
/discoverand marketplace pages rely on search only. - Category names are not yet in the DB, so SEO slugs (
/categories/trading) don't exist. Whop-style category landing pages would need either path (A) extended type values or (B) a propercategoriestable.
Recommendation¶
Path B: a real categories table, but scoped tightly for v1 and introduced in Phase 2 (alongside the storefront handle work) rather than Phase 1.
Why:
- Keeping products.type as a fulfillment dimension (digital / membership / online_class) is correct. It drives per-type UX. It is not what buyers browse by.
- Whop-parity discovery needs subject-based browsing ("Trading", "Ecom", "AI & Automation", …). Stuffing those into products.type conflates two orthogonal dimensions and will have to be undone later.
- A categories table with slug, name_id, name_en, optional icon, parent_id, sort_order lets us ship proper SEO-indexed category landing pages in Phase 2 for near-zero incremental cost.
- Backfill: seed a starter taxonomy from the user (7–12 top-level categories) in the migration itself; add a product_categories join table so a product can be tagged with 1–3.
Scope for v1 introduction:
- Single level (no nested subcategories); parent_id stays nullable for a v1.5 hierarchy later.
- Seed list owned by admin; no UX for creators to invent new categories.
- Update the broken frontend chip row to hit /categories endpoint, drop the hard-coded course chip entirely.
Fix that should ship regardless of path¶
Remove the broken 'course' chip from product-list.tsx and my-products.tsx now. It is already misleading on the live site. Small cleanup, zero risk. This is fair game to include in Phase 1 as part of the catalog polish.
Heads-up on migration numbering¶
The migrations plan in the master prompt assumes the next free number is 010. On the live main, migrations 010, 011, and 012 are already shipped (added during the batch-3 sprint for product type fields, community link, and product image remote_url respectively). The first new migration for Phase 1 will be 013; the whole Appendix C index should be re-numbered by +3.