Skip to content

Sorting

Every customer-facing list on the storefront is admin-curated by default. Operators drag rows in the admin panel, the panel sends a single reorder call, and the public client API picks up the new order on the very next request — no cache flush, no deploy.

This guide documents:

  1. The sort_order column convention used across the schema.
  2. The admin reorder API for every curated resource.
  3. The client-side sort knobs (?sort=, ?variant_sort=) that override the default ordering when the storefront needs them.

The sort_order column

Every curated table carries the same shape:

sort_order INTEGER DEFAULT 0
CREATE INDEX idx_<table>_sort_order ON <table> (sort_order);

Tables with sort_order today:

TableSurfaced on client API
brandsGET /catalog/brands
categoriesGET /catalog/categories
tagsGET /catalog/tags
product_typesGET /products/types
productsGET /products (list)
product_variantsvariants[] inside GET /products / GET /products/:id
payment_methodsGET /payment-methods
faqsGET /faq
homepage_banners, homepage_sections, homepage_section_itemsGET /homepage

Default ordering on every list endpoint is:

ORDER BY sort_order ASC, id ASC

id is the deterministic tiebreaker so equal sort_order values still produce a stable page — important for pagination.


Admin reorder API

A single canonical contract is used across every curated resource. The body is a ReorderDto:

{
"items": [
{ "id": 7, "sort_order": 0 },
{ "id": 2, "sort_order": 1 },
{ "id": 11, "sort_order": 2 }
]
}
RuleBehaviour
items.length1 – 500 per request. 0 returns 400. >500 returns 400.
Duplicate idsRejected with 400.
Unknown idsRejected with 404 (<entity> id X not found).
AtomicityEvery update runs inside one Prisma $transaction. Either all rows are updated or none.
Response{ success: true, message: "<Resource> reordered", data: { updated: <count> } }

Endpoint matrix

MethodPathResource
PATCH/admin/brands/reorderBrands
PATCH/admin/categories/reorderCategories
PATCH/admin/tags/reorderTags
PATCH/admin/product-types/reorderProduct types
PATCH/admin/payment-methods/reorderPayment methods
PATCH/admin/products/reorderProducts (global ordering across the catalogue)
PATCH/admin/products/:productId/variants/reorderVariants inside one product
POST/admin/homepage/banners/reorderHomepage banners
POST/admin/homepage/sections/reorderHomepage sections
POST/admin/homepage/sections/:sectionId/items/reorderItems inside one homepage section

Example

PATCH /admin/products/42/variants/reorder
Authorization: Bearer $ADMIN_TOKEN
Content-Type: application/json
{
"items": [
{ "id": 311, "sort_order": 0 },
{ "id": 309, "sort_order": 1 },
{ "id": 312, "sort_order": 2 }
]
}

Response:

{
"success": true,
"message": "Variants reordered",
"data": { "updated": 3 }
}

The change is visible immediately on the next GET /products/42 call.


Client-side overrides

The default sort_order ASC, id ASC ordering is always available, but the client may request a different order via query params on the list endpoints.

GET /products?sort=

?sort=sort_order.asc # default
?sort=sort_order.desc
?sort=name.asc
?sort=name.desc
?sort=price.asc # cheapest first (computed from cheapest variant)
?sort=price.desc
?sort=newest # created_at DESC

Unsupported values fall back to the admin-curated default.

GET /products & GET /products/:id?variant_sort=

The variant array embedded in each product is independently sortable:

?variant_sort=price # default — cheapest first, then admin sort_order, then id
?variant_sort=sort # admin-curated (matches /admin/products/:id/variants/reorder)
?variant_sort=name # alphabetical by variant name
ModeResulting ORDER BY
priceprice ASC, sort_order ASC, id ASC
sortsort_order ASC, id ASC
namename ASC, id ASC

Catalog endpoints — ?sort=

GET /catalog/categories, /catalog/brands, /catalog/tags all accept:

?sort=sort_order.asc # default
?sort=sort_order.desc
?sort=name.asc
?sort=name.desc
?sort=id.asc
?sort=id.desc

Predictable pagination

Because every endpoint uses id as the secondary key, pagination is stable even when many rows share the same sort_order (for example, right after a fresh import where everything is 0). Operators can safely drag-reorder a single row without worrying about the rest of the page shifting under them.


  • Admin product management — where to set sort_order inline when creating a product/variant.
  • Orders — order ordering is unrelated; orders sort by created_at DESC by default.