Categories
Feature deep dive. Service identity lives in Commerce Overview; category field tables in Domain Model §3.9.
Categories organize products into a hierarchy. They come in two types: SYSTEM categories (read-only, shared across merchants of the same business type) and custom categories (merchant-owned, fully editable).
REST Endpoints
Full reference: live OpenAPI at
/v1/api/commerce/doc/openapi.json. Standard CRUD viaControllerFactory; base path/categories.
There are no custom routes (no /categories/tree, no /categories/{id}/move, no /categories/{id}/breadcrumb). There is no CategoryService — the controller handles all custom logic directly.
Category Types
| Type | merchantId | Editable | Visible to |
|---|---|---|---|
SYSTEM | null | No (403 on update/delete) | Merchants matching the category's businessType |
| Custom | Merchant ID | Yes | Only that merchant |
Category Roles (Category.type)
Category.type (CategoryTypes: REGULAR / COMBO / ADDON / FBT) is an FE-grouping label only — it is not an inventory discriminator. Nothing in the inventory or sale path branches on it.
The structural "is this a virtual combo?" decision lives on ProductVariant.type (ProductVariantTypes.COMBO, beside KIT). The COMBO / ADDON / FBT relations live in the single ProductBundler table (ProductBundler.type). See ADR-0003 and the cross-package combo explosion ADR.
Because
Category.typeno longer drives behavior, re-typing a category or moving a Product between categories is safe — it cannot flip a variant between physical and virtual.
Merchant-Scoped Access
For non-admin users, find() requires a merchantId in the filter. The controller then returns the union of:
- SYSTEM categories where
businessTypematches the merchant'sbusinessType - Custom categories where
merchantIdmatches
SYSTEM Category Protection
updateById()blocks updates to categories wheretype === 'SYSTEM'→ HTTP 403deleteById()blocks deletion of SYSTEM categories → HTTP 403
Deletion Policy
Category deletion goes through DeletionPolicyService.deleteCategoryById():
Merchant's strictCategoryDeletion | Category has products | Behavior |
|---|---|---|
true (default) | Yes | HTTP 409 — cannot delete |
true | No | Delete allowed |
false | Yes | Products' categoryId set to null, then category deleted |
false | No | Delete allowed |
The deletion policy is configured per merchant via PUT /merchants/{id}/deletion-policy.
Related Pages
| Page | Description |
|---|---|
| Commerce Overview | Service identity + catalog |
| Domain Model | Category field tables |
| Products | Product catalog |
| ADR-0003 | Why Category.type is grouping-only |