Content-types og blocks
4. Content-types og blocks
Tilføjer du nye content-types og blocks ofte — det er kerne-loopet for at ekstendere CMSen. Denne side viser eksempler du kan kopiere.
En typisk content-type
src/sitetypes/artist-portfolio/types/artwork.ts:
import { registerContentType, type ContentTypeDefinition } from "@/engine";
export const artworkTypeDefinition: ContentTypeDefinition = {
name: "Artwork",
label: { en: "Artwork", da: "Kunstværk" },
layer: "sitetype",
category: "art",
extends: "BaseContentItem",
defaultView: "default",
views: [{ name: "default", description: "Single-artwork page" }],
fields: [
{ type: "Text", name: "title", label: { en: "Title", da: "Titel" }, required: true },
{ type: "Number", name: "year", label: { en: "Year", da: "År" } },
{ type: "Money", name: "price", label: { en: "Price", da: "Pris" } },
{ type: "Image", name: "image", label: { en: "Image", da: "Billede" }, required: true },
{ type: "Boolean", name: "forSale", label: { en: "For sale", da: "Til salg" } },
],
};
let registered = false;
export function registerArtworkType(): void {
if (registered) return;
registerContentType(artworkTypeDefinition);
registered = true;
}
Nøgle-points:
name— unique på tværs af engine. Bruges som type-key i storage og blockslabel— LocalizedString (en + da). Vises i admin-UIlayer—"engine"(basic) eller"sitetype"(specifikt)extends— base-type (typisk"BaseContentItem"eller"Page"). Field-inheritance sker automatiskfields— array af FieldDefinitions. Hver field har type + name + label + valgfri requiredregisteredflag — idempotent registration
En typisk block
src/sitetypes/docs/blocks/docs-hero.tsx:
import { registerBlock } from "@/engine/blocks";
interface DocsHeroConfig {
title?: string;
subtitle?: string;
intro?: string;
ctaLabel?: string;
ctaHref?: string;
}
function DocsHeroRender({ config }: { config: Record<string, unknown> }) {
const c = config as DocsHeroConfig;
if (!c.title?.trim()) return null;
return (
<section className="docs-hero">
<h1>{c.title}</h1>
{c.subtitle && <p>{c.subtitle}</p>}
</section>
);
}
let registered = false;
export function registerDocsHeroBlock(): void {
if (registered) return;
registerBlock({
definition: {
name: "DocsHero",
label: { en: "Docs Hero", da: "Docs Hero" },
layer: "sitetype",
composable: true,
fields: [
{ type: "Text", name: "title", label: { en: "Title", da: "Titel" }, required: true },
{ type: "Text", name: "subtitle", label: { en: "Subtitle", da: "Undertitel" } },
],
defaultConfig: { title: "", subtitle: "" },
},
Render: DocsHeroRender as never,
});
registered = true;
}
Nøgle-points:
- Block har definition (schema for admin-editor) + Render (komponent der render'es public-side)
composable: true— kan tilføjes til BlockArrays- Fields er samme shape som content-types
- Default-config sætter tomme strings så form-editor render'er rene felter
- Render skal returnere
nullhvis content er tom (undgår blank-block-fejl)
Tilføj til Frontpage's allowedBlocks
En block skal være i allowedBlocks på en content-type for at blive vist i palette'n:
// sitetypes/artist-portfolio/types/frontpage.ts
allowedBlocks: ["Hero", "DocsHero", "AboutRollup", "Oprulninger", "ContactSection", "Columns", "SocialFeed", "SocialCTA"],
Vigtig: rendering er ikke begrænset af allowedBlocks — kun palette-visning. Storage kan teknisk indeholde blocks der ikke er allowed; de render'es stadig korrekt hvis blocken er registreret.
Registrér din nye type/block
For content-type: tilføj registerArtworkType() til sitetypens bootstrap*()-funktion.
For block: tilføj registerDocsHeroBlock() til sitetypens bootstrap.
Bootstrap-funktionerne kaldes fra engine/bootstrap.ts (engine-side) eller sitetypes/index.ts (sitetype-side).
Storage shape
Når en content-type record gemmes:
PartitionKey: <tenant-slug>:Artwork
RowKey: artwork_123 (slug eller UUID)
Fields: title="Solnedgang", year=2025, price=12500, image="<storage-path>", forSale=true
Metadata: sequentialId=42, status="published", createdAt, updatedAt
Felter på top-niveau er primitiver (string/number/bool). Complex shapes (arrays, objects) gemmes som JSON-strings og parse'es ved read.
BlockArray-felter (typisk på Pages): hvert block-instance er én JSON-objekt i array'et med shape { name, config, anchor? }.
Test din nye type/block
- Sæt typecheck (
npx tsc --noEmit) — fanger field-name typos - Restart dev-server (engine-bootstrap kører kun ved start)
- Gå til
/admin/<modul>/nyog prøv at oprette en record - Brug Storage Explorer til at se den faktiske data-row
- Gå til en page hvor blocken bruges og verificér public-rendering
Common pitfall: glemt at registrere blocken før Block-array field registreres — sker hvis du tilføjer blocken efter registerBlockArrayField() i bootstrap.ts. Fix: flyt din registerXxxBlock() op før registerBlockArrayField().