Logotipo Hedhog

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

FieldTypeRequiredNotes
slugstringyes
typeCmsLayoutType (vertical | horizontal | grid | blog_post | custom)noDefault: vertical
is_activebooleannoDefault: true
schemaobjectno
default_configobjectno
localeRecord<string, { name: string; description?: string }>yese.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

FieldTypeRequiredNotes
codestringyesUnique within the layout, e.g. "header", "main"
ordernumbernoDefault: 0
configobjectno
localeRecord<string, { name: string; description?: string }>yese.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)