Get Hedhog updates in your inbox
New releases, fresh recipes, and breaking changes — no spam.
cms Submodule
layouts
Manages reusable page layouts (a named arrangement of regions such as header/main/sidebar) that pages reference via layout_slug, plus CRUD over each layout's regions.
Source path: libraries/cms/src/cms-layout.controller.ts, cms-layout.service.ts
Module file: cms.module.ts
Introduction
The Layouts submodule owns `cms_layout` and its child table `cms_layout_region`. A layout has a `type` (vertical, horizontal, grid, blog_post, custom), an optional `schema` and `default_config` (both free-form JSON), an `is_active` flag, and a locale-translated name/description stored in a companion `cms_layout_locale` table. Regions belong to exactly one layout via `layout_id`, carry a `code` that must be unique within that layout (e.g. "header", "main", "sidebar"), an `order`, an optional `config` JSON blob, and their own locale-translated name/description in `cms_layout_region_locale`. A page's `layout_slug` is resolved to `layout_id` at page create/update time by the Pages submodule, and every component instance placed on a page must reference one of that page's own layout's region codes — the region/layout relationship is what the Pages submodule's `saveComponents` validates against.
Create and update on both layouts and regions follow the same two-step pattern used across the CMS module: the base row is written first, then each entry in the `locale` map is upserted — updated if a `cms_layout_locale`/`cms_layout_region_locale` row already exists for that locale, created otherwise — so submitting a translation for only one locale never touches the others. Neither layouts nor regions are soft-deleted: `DELETE /cms/layouts/:id` and `DELETE /cms/layouts/:id/regions/:regionId` issue a hard Prisma `.delete()`. Region deletes are additionally scoped to their parent layout (`layout_id` must match `:id` in the path), so a region ID belonging to a different layout returns 404 rather than deleting cross-layout.
There is no explicit guard in `CmsLayoutService` against deleting a layout that pages still reference — the protection comes entirely from the database: `cms_page.layout_id` is declared `onDelete: RESTRICT` in the table YAML, so attempting to delete an in-use layout surfaces as a raw foreign-key constraint failure rather than a friendly application-level error. The response shape also includes a `pages_count` field intended to show how many pages use a layout, but the underlying Prisma queries in `list()` and `getById()` do not request a `_count` relation — `mapLayout()` reads `layout._count?.cms_page ?? 0`, which is always `undefined` in practice, so `pages_count` currently reports `0` for every layout regardless of actual usage.
Unlike Pages, where `cms-editor` can create and update content, every write endpoint in this submodule (layouts and regions alike) is restricted to `admin`/`admin-cms` only — content editors can read layouts to pick one for a page but cannot define or modify layouts or their regions themselves.
HTTP Endpoints
9 endpoints
Full listing of layouts (not paginated), each with its regions embedded, ordered by ID. Names/descriptions resolve to the request's locale (falling back to the first available translation).
- Query
- slug? — filter to a single layout by slug
Create a new layout and its per-locale name/description rows.
Body
| Field | Type | Required | Notes |
|---|---|---|---|
| slug | string | yes | — |
| type | CmsLayoutType (vertical | horizontal | grid | blog_post | custom) | no | Default: vertical |
| is_active | boolean | no | Default: true |
| schema | object | no | — |
| default_config | object | no | — |
| locale | Record<string, { name: string; description?: string }> | yes | e.g. { "en": { "name": "...", "description": "..." } } — upserts one locale row per key |
Get a single layout with its regions, localized to the request's locale.
- Params
- id (int)
Update a layout (same fields as create, all optional). Locale entries are upserted individually, leaving untouched locales as-is.
- Params
- id (int)
Hard-delete a layout. Not guarded at the service level — fails with a database foreign-key error if any page still references it (cms_page.layout_id is RESTRICT).
- Params
- id (int)
List a layout's regions, ordered by their order field, localized to the request's locale.
- Params
- id (int) — layout ID
Add a region to a layout and its per-locale name/description rows.
- Params
- id (int) — layout ID
Body
| Field | Type | Required | Notes |
|---|---|---|---|
| code | string | yes | Unique within the layout, e.g. "header", "main" |
| order | number | no | Default: 0 |
| config | object | no | — |
| locale | Record<string, { name: string; description?: string }> | yes | e.g. { "en": { "name": "...", "description": "..." } } — upserts one locale row per key |
Update a region (same fields as create, all optional). Rejects with 404 if regionId does not belong to layout id.
- Params
- id (int) — layout ID, regionId (int)
Hard-delete a region. Rejects with 404 if regionId does not belong to layout id. Any cms_page_component rows pointing at the region are subject to its RESTRICT foreign key, so deleting a region still in use by page components will fail at the database level.
- Params
- id (int) — layout ID, regionId (int)