# All products are assigned to at least one category

- **Pattern:** `ab-001115` (`ecommerce-catalog.product-data.category-assignment`)
- **Severity:** high
- **Lifecycle:** active
- **Last modified:** 2026-04-18
- **Canonical URL:** https://auditbuffet.com/patterns/ab-001115
- **License:** CC-BY-4.0 — attribute to AuditBuffet Pattern Catalog (https://auditbuffet.com/patterns/ab-001115)

## Why it matters

Products without a category assignment become invisible to browsing and filtering — they exist in the database but can't be discovered through any navigation path other than direct search. Beyond UX, ISO 25010:2011 functional-correctness treats uncategorized products as broken catalog behavior: the product is present but unreachable. A nullable `categoryId` foreign key means every product creation path in the codebase can skip assignment, and there's no audit trail for which products were left uncategorized. At scale, this creates catalog rot: orphaned products accumulate, promotional campaigns miss items, and inventory counts diverge from what customers see.

## Severity rationale

High because uncategorized products disappear from browse and filter paths, making inventory effectively invisible to customers navigating the catalog — a silent revenue leak.

## Remediation

Add a non-optional many-to-many relationship between `Product` and `Category` in `prisma/schema.prisma`, and enforce at least one category assignment in `src/app/api/products/route.ts`.

```prisma
model Product {
  id         String     @id @default(cuid())
  name       String
  categories Category[]
}

model Category {
  id       String    @id @default(cuid())
  name     String
  products Product[]
}
```

In the product creation route, reject requests with an empty categories array:

```ts
if (!categoryIds || categoryIds.length === 0) {
  return Response.json({ error: 'At least one category is required' }, { status: 400 })
}
```

## Detection

- **ID:** `category-assignment`
- **Severity:** `high`
- **What to look for:** Count all category-related schema definitions (Category model, join tables, foreign keys). Enumerate every product creation path (API routes in `src/app/api/products/`, admin forms, seed scripts) and check whether category assignment is required during creation. Before evaluating, extract and quote the category relationship definition from the product schema.
- **Pass criteria:** A category relationship exists in the product schema (many-to-many join table or foreign key) and at least 1 product creation path enforces category assignment as required (not optional). Count all product creation paths found and report: "X of Y creation paths enforce category assignment."
- **Fail criteria:** The schema allows products without category assignment (nullable foreign key, optional relation), or the codebase contains product creation logic that does not require a category.
- **Do NOT pass when:** A category field exists but is nullable or optional in the schema, even if the UI happens to always include it — the database constraint must enforce it.
- **Skip (N/A) when:** The project uses a flat catalog with no category concept (e.g., single-product SaaS, digital downloads with no grouping).
- **Cross-reference:** For category hierarchy and navigation patterns, the SEO Fundamentals audit covers URL structure and site architecture.
- **Cross-reference:** For database relationship patterns, the Database Design & Operations audit covers join table design and referential integrity.
- **Cross-reference:** For admin form validation, the Error Resilience audit covers form validation and user input handling.
- **Detail on fail:** `"Product schema has no category relationship — no Category model or join table found"` or `"Product creation in src/app/api/products/route.ts allows categoryId to be null"`
- **Remediation:** Add a many-to-many relationship between products and categories in `prisma/schema.prisma`:

  ```prisma
  model Product {
    id String @id @default(cuid())
    name String
    categories Category[]
  }

  model Category {
    id String @id @default(cuid())
    name String
    products Product[]
  }
  ```

  Enforce that at least one category is assigned in `src/app/api/products/route.ts` when creating a product.

## External references

- iso-25010 functional-correctness

Taxons: data-integrity

HTML version: https://auditbuffet.com/patterns/ab-001115
