Skip to content

Catalogue

Products and everything that classifies them. The backend catalogs module owns it; the storefront shows it; the admin manages it.

How this page was verified

Read on 2026-09-15 from the backend src/catalogs/, the storefront /products routes and the admin /admin/products area.

The model

A product groups articles. An article is one buyable variant (a SKU with size and colour); stock is tracked per article. Products are classified several ways:

  • head → type → subtype — the primary product tree (product_heads, product_types, product_subtypes). The storefront PLP defaults its head to NEXT_PUBLIC_BAG_ID.
  • category → sub-category (categories, sub_categories).
  • brand, series, collaboration, tags, color codes, icons.

Supporting tables: narratives, size_charts, product_on_sales, product_recommendations, and image/banner/video tables. Table names are the @Entity({ name }) values; the full list is on the Database page. Most text fields are bilingual (nameId/nameEn, …).

Backend

src/catalogs/ — resolvers split by audience:

  • CMS (products.resolver and per-classifier resolvers): products, product, productArticles, productArticle, productArticleQuantity, delete mutations, and CRUD for heads/types/subtypes, categories, brands, series, collaborations, tags, color-codes. Product create/update is REST, not GraphQL — the GraphQL create/update are commented out; the admin posts multipart to catalogs/products (and /:productId/articles, /update-quantity).
  • Storefront (storefront-*.resolver.ts, front* ops): frontProducts, frontProduct, frontSearchProducts, frontProductOfMetaUrl, frontProductOfShortcode, frontProductRecommendations, frontProductNewArrivals, frontProductOnSales, frontProductSizes, frontCategories, frontProductHeads, frontProductTypes, frontSpecialProductType, frontSeries*, frontCollaboration(s), frontTags, getPopularSearchKeywords.

Stock comes from the WMS through WmsService — see Stock (WMS).

Storefront

  • PLP /products (src/app/products/page.tsx) reads ?head=&subcategory=&type=; UI in src/modules/products/components/, data hook src/hooks/useProducts.tsx, GraphQL in src/lib/products/, src/lib/product-heads/, src/lib/product-types/.
  • PDP /products/[slug] (src/app/products/[slug]/page.tsx, with a server layout.tsx that fetches by metaUrl for SEO). UI in src/modules/product-details/components/ — media, variants, add-to-cart/buy-now (product-cart-form.tsx), prices, tier discount, flash-sale banner, reviews, share. Image zoom via react-zoom-pan-pinch.
  • Short link /s/[shortcode] resolves a product by shortcode and redirects to /products/{metaUrl}.
  • Related listings: /series, /collaboration, /new-arrival, /sale, /search (behind NEXT_PUBLIC_FEAT_SEARCH).

Admin

/admin/products (src/app/admin/products/page.tsx) with add-product, edit-product, and variants under [slug]/add-variant / edit-variant. The form is tabbed (Detail + SEO) and submits FormData to catalogs/products via axios (src/lib/products/product.rest.ts), because it uploads images. Classifier CRUD lives in sibling areas: heads-types, categories, sub-categories, brands, tags, series, collaborations, color-codes, icons, category-icons.

Gotchas

  • Product write is REST, read is GraphQL. Do not look for a createProduct mutation — it is a REST controller.
  • Stock is not stored on the product. Quantity comes from the WMS; productArticleQuantity / frontProductSizes reflect it. See Stock (WMS).
  • synchronize: true — a new classifier or field is an entity change, not a migration. See Database.