Layout regions
Your site declares which regions a layout block can live in. The editor lists exactly those.
Header and footer are not the only places a layout block can live. Since cmssy-sdk 13 the site declares its regions in cmssy.config.ts, and the editor lists exactly those under Layouts. A sidebar, a top bar, a cookie strip - whatever your app actually mounts.
If you declare nothing, you get header and footer, the same as before.
Declaring regions
// cmssy.config.ts
import { defineCmssyConfig, defineCmssyLayout } from "@cmssy/next";
export const layout = defineCmssyLayout({
regions: [
{ id: "header", label: "Header" },
{ id: "sidebar", label: "Sidebar" },
{ id: "footer", label: "Footer" },
],
});
export const cmssy = defineCmssyConfig({
org: process.env.CMSSY_ORG_SLUG,
workspaceSlug: process.env.CMSSY_WORKSPACE_SLUG,
draftSecret: process.env.CMSSY_DRAFT_SECRET,
apiUrl: process.env.CMSSY_API_URL,
layout,
});An id starts with a letter or digit and continues with letters, digits, _ or - (up to 50 characters). Ids are unique, labels are what the editor shows, and a layout holds at most 20 regions. defineCmssyLayout throws at startup on anything else - a typo does not reach the editor.
Keep the regions array inline in the call. Hoisted into a variable first, TypeScript widens the ids to string and you lose the typing below.
Typed slots
CmssyRegion<typeof layout> is the union of your declared ids. Type the slot helper with it and a slot for a region you never declared does not compile:
import type { CmssyRegion } from "@cmssy/next";
import { cmssy, type layout } from "@/cmssy.config";
const slot = (position: CmssyRegion<typeof layout>) => (
<CmssyLayoutSlot config={cmssy} blocks={blocks} position={position} path={path} />
);
{slot("header")}
<aside>{slot("sidebar")}</aside>
<main><CmssyPage {...props} /></main>
{slot("footer")}Import layout as a type only. The config module reads server env, and a value import from a client component would pull it into the browser bundle.
The slot fetches the layouts of the routed page - path decides, and a page inherits its parent's regions as usual. Pass page only to pin a slot to one page regardless of the route (with the locale form, which has no path, you have to).
Which blocks a region accepts
A block opts into regions with layoutPositions. The editor offers it only in those regions:
export const sidebarNavBlock = defineBlock({
type: "sidebarNav",
label: "Sidebar navigation",
layoutPositions: ["sidebar"],
component: SidebarNav,
props: sidebarNavProps,
});A block without layoutPositions is a page block and never appears in a layout.
How the editor learns about them
On Next, createCmssyPage and createCmssyEditPage forward the regions to the editor on their own. On Astro and React Router, loadCmssyPage / createCmssyLoader return layoutRegions - pass it to CmssyEditor as edit={{ editorOrigin, layoutRegions }}. Delivery returns the layout groups keyed by your ids, so groups.find(g => g.position === "sidebar") works as you would expect.
Removing a region
Take a region out of regions and its blocks are not deleted. The editor keeps the card, marks it as not mounted by the site, and lets you move or remove the blocks yourself. Nothing disappears from a page because a deploy changed a config file.
Requires TypeScript 5.0 or newer (the typing relies on const type parameters). See Layouts for inheritance and the public/edit split, and the sdk migration guide v12-to-v13 for the rename from LayoutPosition.