.cratly.config.yaml specification
This document is the living, framework-agnostic specification for the .cratly.config.yaml file format. It is owned by cratly (this repository), not by any single theme framework — Scavold is the reference adapter, but the format is meant for any adapter (Next.js, Astro, …). Update it whenever a key is added or changed.
Companion: the section-type schema & manifest convention specifies how the editor learns the typed properties each section kind accepts.
Background
"cratly" is an acronym for "a content repository and a template engine". It is an online editor for managing Markdown and media files stored in a content repository. Changes committed through cratly trigger an attached continuous delivery pipeline, which invokes a build tool - the template engine - to turn the Markdown and media files into a published website.
Purpose and design goals
.cratly.config.yaml describes a website — its content structure, media assets, and editorial metadata. It deliberately does not describe build tools, frameworks, or deployment targets. Any compliant tool can read it without knowledge of the underlying tech stack.
Two classes of tools are expected to consume this file:
- Editors such as cratly use it to know where content lives, how to present frontmatter fields to authors (pickers, toggles, references to other pages), and what constitutes valid input.
- Theme framework adapters such as Scavold (VitePress) use it at build time to locate source files, process assets, and apply content rules such as locale inheritance or field value fallback.
Third parties are explicitly welcome to build adapters for other frameworks (Next.js, Nuxt, Astro, …) against this same file. Keys should therefore remain semantic and content-oriented. If a concept only makes sense inside one specific build tool it does not belong here.
File location
The file must reside at the project root alongside package.json. Both editors and adapters resolve it relative to the working directory.
Top-level keys
version
version: 0Declares which version of this specification the file conforms to. Tools should reject or warn on versions they do not support.
| Value | Meaning |
|---|---|
0 | Pre-stable. Breaking changes may occur at any time. |
Optional while the spec is at version 0. Once a stable 1 is released this key becomes required and tools must validate it.
pages_folder
pages_folder: pagesPath to the folder that contains all Markdown page files, relative to the project root. Editors look here when browsing or linking pages. Adapters use it as the source root for building the page hierarchy and compiling redirects.
Required. No default — both editors and adapters must reject a configuration that omits this key.
media_folder
media_folder: mediaPath to the folder that contains media files (images, videos, documents) referenced from Markdown, relative to the project root. Editors open this folder when a field with type: media-file is edited. Adapters resolve image paths against it.
Optional. When omitted, adapters may fall back to pages_folder.
static_folder
static_folder: publicPath to the folder that contains static theme assets (icons, fonts, brand images, manifest files) that should be served at the root of the web server but are not managed by content editors. Relative to the project root.
This folder is intentionally separate from media_folder and pages_folder: editors never browse or write to it, and its contents are owned by the theme or build configuration, not by the site's authors.
Adapter behaviour depends on the build tool:
- Scavold / VitePress / Vite — maps to Vite's
publicDir; files are copied verbatim into the build output and served at their path relative to the site root. - Other adapters — equivalent to whatever the underlying framework calls its "static" or "public" directory.
Optional. When omitted and pages_folder resolves to a subfolder, Scavold defaults to a public/ folder at the project root to keep static assets outside the editor-visible content tree. When the build tool has no equivalent concept the key is ignored.
image_widths
image_widths: [320, 640, 960, 1280, 1920]List of pixel widths at which responsive image variants are generated during the build. Widths larger than the source image are silently skipped so images are never upscaled. The largest entry in the list acts as the cap; if the source is narrower than all entries only the source width is used.
Optional. Adapters apply a built-in default equivalent to the example above when this key is absent.
image_sizes
image_sizes: "100vw"Default value for the HTML sizes attribute placed on every responsive image that does not carry a per-image override. Accepts any valid CSS sizes string.
Per-image overrides are written in the Markdown image title field:
Optional. Adapters default to 100vw when absent.
locales
locales:
- code: de
flag: 🇩🇪
label: Deutsch
- code: de-AT
flag: 🇦🇹
label: Deutsch (Österreich)
- code: en
label: EnglishDeclares the set of locales the site supports. Used by editors to populate locale pickers (for lang, translations, and redirect frontmatter fields) and by adapters to validate locale tags and drive locale-aware processing.
When this key is absent, editors fall back to a built-in list of common locales. When it is present, only the listed locales are offered.
Each entry is an object with the following fields:
| Field | Required | Description |
|---|---|---|
code | Yes | BCP 47 language tag. Simple tags (de, en) and region subtags (de-AT, zh-TW) are both valid. |
flag | No | Unicode flag emoji representing this locale (e.g. 🇦🇹). Editors display it next to the locale code. When absent, editors derive a flag from the primary language subtag if it is part of their built-in list, and fall back to a neutral placeholder otherwise. |
label | No | Human-readable name of the locale in its own language (e.g. Deutsch (Österreich)). Used as a tooltip or label in editors. When absent, editors display the code value. |
Optional. When absent, editors use their own built-in locale list.
containers
containers:
<name>: <string> # shorthand — component name only
<name>: # full object form
[component: <string>]
[label: <string>]
[props: {<prop-name>: {type: <type>, ...}, ...}]
[flags: [<flag-name>, ...]]Declares the set of named Markdown containers the site uses, beyond the built-in containers which are always available without declaration:
- HTML sectioning elements:
section,aside,article,header,footer,nav,main video
Editors use this list to offer a constrained pick-list of container types when inserting or editing a container block, rather than accepting free-form names. Each declared container may advertise a set of typed properties (see props); the editor renders an appropriate control for each (text input, checkbox, number, media picker, page picker, select) in its section editing UI.
The set of section kinds and their props is resolved by the adapter and emitted into the website repository as a machine-readable section-type manifest that the editor reads — see the section-type schema & manifest convention. The declarations here are merged on top of the adapter's built-in section definitions; a site need only declare custom sections and overrides, never restate built-in props.
Built-in containers can be declared here purely to add a label, props, or flags for the editor — the component mapping is already set by the adapter and does not need to be repeated unless a custom component is desired.
Each entry under containers is keyed by the container name as it appears in Markdown after the opening :::. Two value forms are accepted:
String shorthand
containers:
hero: MyHeroComponentA plain string is treated as an explicit component name by adapters. Editors that do not recognise the string form fall back to the raw container name as the label. Use this form when no editor metadata (label, props, flags) is needed.
Object form
containers:
hero:
component: MyHeroComponent
label: "Hero section"
flags:
- dark
- centeredThe full object form supports all properties described below. All properties are optional; an empty object {} is valid and registers the container with the adapter's default component-naming convention (Scavold{Name}).
component
component: MyHeroComponentOverrides the Vue component the adapter uses to render this container. When absent, adapters apply their default naming convention (e.g. hero → ScavoldHero).
Optional.
label
label: "Hero section"Human-readable name shown in the editor's container type picker. When absent, editors fall back to the raw container name.
Optional.
props
props:
image:
type: media-file
label: "Background image"
columns:
type: number
label: "Columns"
default: 2
variant:
type: enum
label: "Variant"
values: [ plain, boxed, dark ]
default: plain
dark:
type: boolean
label: "Dark mode"Map of typed properties the section accepts. Each entry is keyed by the property name an author writes on the container's opening line and uses the same type vocabulary as frontmatter_fields, plus enum:
type | Editor widget | Markdown serialization |
|---|---|---|
text | single-line input | name="value" |
textarea | multi-line input | name="value" |
boolean | checkbox | bare flag token when true |
number | numeric input | name="value" |
media-file | media picker | name="path" (resolved against media_folder) |
page-ref | page-tree picker | name="path" (resolved against pages_folder) |
enum | select | name="value" |
Per-prop attributes: label, hint, required, default, and — for enum only — values (a list of strings, or {value, label} objects). A string shorthand is accepted for the type alone (image: media-file).
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:
::: hero dark image=/media/banner.jpg variant=boxed
Content here.
:::Optional. See the section-type schema for the authoritative field list and the manifest the editor consumes.
flags
flags:
- dark
- centered
- fullwidthShorthand for a set of boolean props — each listed name becomes a checkbox in the editor. Equivalent to declaring each under props with type: boolean. Authors may still write any flag by hand in Markdown; this list only controls what the editor surfaces. When the same name appears in both props and flags, the props entry wins.
Optional.
Example
containers:
# shorthand — just register the component, no editor metadata needed
teaser: TeaserCard
# full form — typed props plus component override
hero:
component: SiteHero
label: "Hero section"
props:
image: { type: media-file, label: "Background image" }
dark: { type: boolean, label: "Dark variant" }
# editor metadata only — adapter uses default ScavoldCallout component
callout:
label: "Callout box"
flags:
- warning
- info
# override a built-in — replace ScavoldVideo with a custom player
video:
component: MyVideoPlayer
label: "Video"In Markdown a container with props looks like (boolean props as bare flags, all other types as name=value pairs):
::: hero dark image=/media/banner.jpg
Content here.
:::frontmatter_fields
frontmatter_fields:
<field-name>:
type: <type>
[inherits: true]
[label: <string>]
[hint: <string>]Declares custom frontmatter fields that both editors and adapters treat specially. Fields not declared here are still valid frontmatter — they are simply passed through without any special handling.
Each entry under frontmatter_fields is keyed by the exact frontmatter property name used in Markdown files. The value is an object with the following properties:
type
Determines how editors present the field and how adapters process its value.
| Value | Editor widget | Adapter behaviour |
|---|---|---|
text | Single-line text input | Passed through as-is |
textarea | Multi-line text input | Passed through as-is |
boolean | Toggle / checkbox | Coerced to boolean |
number | Numeric input | Coerced to number |
media-file | Media folder file picker | Path resolved against media_folder |
page-ref | Page tree picker | Path resolved against pages_folder |
Required on every frontmatter_fields entry.
inherits
inherits: trueWhen true, adapters walk the page hierarchy upward from the current page until a ancestor is found that carries a non-empty value for this field. The resolved (possibly inherited) value is what components and templates receive.
This allows a value set on a parent or section index page to apply automatically to all subordinated pages unless they override it explicitly. Useful for fields such as a header image, a default author, or a content category.
Optional. Defaults to false.
label
label: "Header image"Human-readable label shown by editors next to the input. When absent, editors may derive a label from the field name.
Optional.
hint
hint: "Shown at the top of the page and in link previews."Short explanatory text shown below the input in editors.
Optional.
Built-in frontmatter conventions
The following frontmatter properties are part of the core specification and are understood by all compliant tools without any declaration in frontmatter_fields.
title
title: About usDisplay title for the page. Used in navigation menus, breadcrumbs, the browser <title> element, and social previews. When absent, adapters fall back first to the first ATX heading (# …) in the page body, then to the path segment.
Optional.
label
label: AboutShort navigation label shown in menus and breadcrumbs instead of title. Useful when the full title is too long for compact navigation. Falls back to title and then the path segment when absent.
Optional.
order
order: 3Controls the sort position of this page among its siblings. Pages are sorted by order ascending; pages that omit order follow at the end in filename order. Negative values are permitted.
Optional.
url
url: /custom-path/Declares a root-relative alias URL for the page. The source file stays in its original location in pages_folder but is compiled to this path in the build output. Adapters detect conflicts at build time. Editors that present a page list should resolve this alias rather than the default path when linking to the page.
Optional.
lang / locale
locale: deDeclares the language or locale of a page. Adapters inherit this value downward through the page hierarchy: a value set on a section's index.md applies to all pages in that section unless overridden. Standard BCP 47 language tags are expected (de, en, en-US, …). locale is the preferred key; lang is accepted as a deprecated alias and is ignored when locale is also present.
translations
translations:
en: en/about_us.md
fr: fr/a-propos.mdMaps locale codes to the path of the equivalent page in that locale, relative to pages_folder. Editors present this as a page picker per locale. Adapters use the mapping to generate hreflang link elements and to power locale-aware navigation.
hide
# Hidden everywhere
hide: true
# Hidden from navigation menus only
hide: menu
# Hidden from breadcrumbs only
hide: breadcrumbControls visibility in navigation. Accepts a boolean or one of two string values:
| Value | Effect |
|---|---|
absent / false | Visible in menus and breadcrumbs (default) |
true | Hidden from menus and breadcrumbs; page still served at its URL |
"menu" | Hidden from navigation menus only |
"breadcrumb" | Hidden from breadcrumbs only |
Optional. Defaults to visible when absent.
redirect
# Unconditional redirect
redirect: other-page.md
# Locale-conditional redirects
redirect:
de: de/index.md
en: en/index.md
# Locale-conditional with a catch-all fallback
redirect:
de: de/index.md
en: en/index.md
"*": fallback/index.mdDeclares that visiting this page should redirect the visitor elsewhere. A plain string is an unconditional redirect. An object maps locale codes to target paths, allowing the same URL to redirect differently depending on the visitor's detected locale (browser language preference or previously selected locale).
When the object form is used, the special key "*" serves as a catch-all fallback: adapters redirect to this target when no locale-specific entry matches the visitor's detected locale. If the object contains only a "*" key, adapters may treat it as equivalent to the plain-string form.
Paths are resolved relative to the current page file using the same rules as Markdown links. Only paths within pages_folder are accepted; absolute external URLs are rejected.
Annotated example
version: 0
# Where content and media live
pages_folder: pages
media_folder: media
static_folder: public
# Responsive image generation
image_widths: [320, 640, 960, 1280, 1920]
image_sizes: "100vw"
# Custom frontmatter fields
frontmatter_fields:
image:
type: media-file
inherits: true
label: "Page image"
hint: "Used at the top of the page and in social previews. Inherited by sub-pages when not set."
featured:
type: boolean
label: "Featured"
hint: "Highlight this page in listings."
author:
type: text
inherits: true
label: "Author"
# Named containers beyond the built-in sectioning elements and video
containers:
hero:
label: "Hero section"
flags:
- dark
- centered
callout:
label: "Callout box"
flags:
- warning
- info
# shorthand: just a component override, no editor metadata
teaser: TeaserCardVersioning and extensibility
The version key tracks the version of this specification. The current version is 0, meaning the format is pre-stable and breaking changes may occur. All keys are considered experimental until version 1 is declared.
Tool-specific extension keys should be namespaced to avoid clashes, for example:
_scavold:
some_option: value
_crate:
some_option: valueCompliant tools must ignore keys they do not recognise rather than erroring.