Cratly
Zu Deutsch wechselnDENAV.openEditor

Section types — schema & manifest convention

This document is the framework-agnostic contract that lets the cratly editor render the right controls (text inputs, checkboxes, media pickers, page pickers, selects) when an author inserts or edits a section in a page. It is owned by cratly, not by any single theme framework: Scavold is the reference adapter, but any tool (Next.js, Astro, Nuxt, …) may produce a conforming manifest.

The machine-readable grammar lives beside this document:

The two artifacts

Section-type information is split into a grammar (the shape) and a manifest (the data), because they have different owners and different lifecycles.

ArtifactWhat it isOwnerLifecycle
Grammar (section-types/v0.json)JSON Schema describing what a manifest looks like and the property-type vocabularycratly (this site)Versioned, stable. Tools bundle the version(s) they support.
Manifest (.cratly/sections.json)The actual set of section kinds a given site offers and their typed propsthe adapter (e.g. Scavold), per websiteGenerated into each website repo on build.

Why this design avoids CORS

cratly.io and the editor are served from different hosts, so a naive "editor fetches the schema from cratly.io at runtime" approach would hit CORS. It is avoided by never making that runtime, cross-origin fetch:

  1. The grammar is a versioned contract, not a runtime dependency. A JSON Schema $id is an identifier, not a mandatory fetch target. The editor bundles the grammar version(s) it understands. cratly.io's copy exists for humans, agents, and third-party tooling — and, being a single public static file, may be served with Access-Control-Allow-Origin: * so even direct fetches are unblocked.

  2. The manifest lives in the website repo. The editor reads .cratly/sections.json through the same GitLab API path it already uses for the file tree and Markdown — not a browser fetch to cratly.io. No new cross-origin surface is introduced.

Lifecycle

adapter build (CI)                website repo                 cratly editor
─────────────────                 ────────────                 ─────────────
generate manifest  ───emit───▶   .cratly/sections.json  ──git API──▶  read & merge
from components                  (committed/emitted)               with .cratly.config.yaml
                                                                   → render section controls

The adapter (Scavold) is the single source of truth for its built-in section kinds: their props are generated from the component definitions, not hand-authored. The website's .cratly.config.yaml only adds custom sections and overrides labels/hints — it never restates built-in prop schemas.

Manifest format (.cratly/sections.json)

json
{
  "$schema": "https://cratly.io/schema/section-types/v0.json",
  "specVersion": 0,
  "adapter": { "name": "scavold", "version": "1.4.0" },
  "sections": {
    "section": {
      "label": "Section"
    },
    "video": {
      "label": "Video",
      "hint": "Embedded video with playback controls.",
      "props": {
        "src":      { "type": "media-file", "label": "Video file", "required": true },
        "poster":   { "type": "media-file", "label": "Poster image" },
        "autoplay": { "type": "boolean",    "label": "Autoplay (muted)", "default": false },
        "loop":     { "type": "boolean",    "label": "Loop" },
        "muted":    { "type": "boolean",    "label": "Muted" },
        "preload":  { "type": "enum",       "label": "Preload",
                      "values": ["none", "metadata", "auto"], "default": "metadata" },
        "label":    { "type": "text",       "label": "Accessible label",
                      "hint": "Announced by screen readers when the surrounding text does not describe the video." }
      }
    },
    "hero": {
      "label": "Hero section",
      "props": {
        "image": { "type": "media-file", "label": "Background image" },
        "dark":  { "type": "boolean",    "label": "Dark variant" }
      }
    }
  }
}

Property types

typeEditor widgetMarkdown serializationAdapter processing
textsingle-line inputname="value"passed through
textareamulti-line inputname="value"passed through
booleancheckboxbare flag token (dark) when true, absent when falsecoerced to boolean / CSS class
numbernumeric inputname="value"coerced to number
media-filemedia pickername="path"path resolved against media_folder
page-refpage-tree pickername="path"path resolved against pages_folder
enumselectname="value"validated against values

Serialization rule: a boolean prop is written as a bare flag on the opening line; every other type is written as a name=value pair. This mirrors how Markdown container arguments already work:

markdown
::: hero dark image=/media/banner.jpg
Content here.
:::

required, default, and (for enum) values carry the rest of the editor's control metadata. See the grammar for the authoritative field list.

Relationship to .cratly.config.yaml

The website's .cratly.config.yaml containers block is where a site author declares custom sections and overrides editor metadata for built-ins, using the same property vocabulary as the manifest. The adapter merges, in order (last wins):

  1. Adapter built-in section definitions (generated → .cratly/sections.json)
  2. containers declarations from .cratly.config.yaml
  3. Explicit developer overrides passed to the adapter

The full containers.props syntax is specified in the .cratly.config.yaml spec. In short, the existing boolean-only flags: list becomes sugar for a set of boolean props, and a new props: map adds typed properties:

yaml
containers:
  hero:
    label: "Hero section"
    props:
      image: { type: media-file, label: "Background image" }
      dark:  { type: boolean,    label: "Dark variant" }

Versioning

specVersion tracks this grammar. The current version is 0 (pre-stable; breaking changes may occur). New grammar versions are published at sibling URLs (/schema/section-types/v1.json, …). Tools bundle the versions they support and warn on unknown ones.