Block Schema Declaration

Block templates can declare their full schema co-located in the template file using braised: comment markers. The schema is surfaced by braised inspect and MCP tools — it gives AI agents, documentation contributors, and site maintainers a machine-readable description of every block, including its props, children, and parent relationships.

Schema declaration is optional and progressive: a block without any markers still renders correctly. Add markers to the blocks where introspection accuracy matters most, and skip them on one-off templates.

Why declare a schema

Without markers, custom blocks are opaque to tooling. braised inspect shows schema=undecorated, and braised mcp serve's get_block_list tool omits the block's props, children, and description. An AI agent using get_block_list to help a writer author content will not know the block exists or how to use it.

With markers, the full schema merges with built-in block schemas into a single unified view. braised inspect shows schema=custom, get_block_list returns complete prop information, and an agent can generate correct block syntax on the first attempt.

Marker syntax

All schema metadata is declared in Go template comments at the top of the block template file. Each marker is a self-contained comment line:

{{/* braised:structural */}}
{{/* braised:description: A responsive grid of card child blocks. */}}
{{/* braised:children: card */}}
{{/* braised:parent: layout-section */}}
{{/* braised:prop: columns type=string default=3 description="Number of grid columns." */}}
<div class="card-grid">
  ...
</div>

Placement rules:

  • Markers must be in Go template comments ({{/* ... */}}).
  • They can appear anywhere at the top of the file — the scanner reads all template comments before the first non-comment, non-blank line. Copyright headers and other non-braised comments are skipped; they do not block marker discovery.
  • Order is not enforced, though braised:structural first is conventional.
{{/* Copyright 2026 Acme Corp. */}}
{{/* braised:structural */}}
{{/* braised:description: My block. */}}
<div>...</div>

This works correctly — the copyright line is skipped.

  • The scan stops at the first non-blank line that is not a template comment. Markers embedded mid-template are never read.

All markers

Marker Effect
{{/* braised:structural */}} Declares the block as structural (requires :::{/name} close)
{{/* braised:description: text */}} Short description for braised inspect and MCP responses
{{/* braised:children: a, b */}} Documents which child block types this block expects
{{/* braised:parent: a, b */}} Documents which blocks this block is designed to live inside
{{/* braised:prop: name [key=value...] */}} Declares one accepted prop
{{/* braised:example: markdown source */}} Worked Markdown example returned by MCP tools; use \n for newlines

braised:structural

Declares this block as structural: it requires a named close (:::{/name}) and may contain nested child blocks without the parser consuming intermediate close fences.

This marker has a direct functional effect on parsing. All other markers are metadata only — they influence introspection tools but not the parser or renderer.

See Structural blocks for a full explanation of structural vs leaf semantics.

braised:description:

A short prose description, shown in braised inspect and returned by get_block_list. Write it for a documentation author, not an engineer: what does this block do, and when should they use it?

{{/* braised:description: A responsive grid of card child blocks. Non-card children are silently ignored. */}}

braised:children: and braised:parent:

These fields document the intended parent/child relationship for the block type.

{{/* braised:children: card */}}
{{/* braised:parent: card-grid */}}

Both accept comma-separated lists:

{{/* braised:children: tab, code-tab */}}
{{/* braised:parent: sidebar, main-content */}}

:::callout{type="note" title="What these fields actually do"} braised:children and braised:parent have two effects:

  1. get_block_list returns them — AI agents know the intended nesting structure and can generate correct block syntax without guessing.
  2. braised inspect warns on unexpected children — when a parent declares braised:children, the block tree flags any direct child not in that list with [! unexpected in "parent": declared children=[...]].

They do not affect parsing or rendering. The parser has no concept of invalid children. If you want to restrict which child blocks are rendered, do it in the template with {{if eq .Name "card"}}. The schema fields describe the intended structure; the template enforces it.

braised:prop:

Declares one accepted prop. The format is:

{{/* braised:prop: propname [key=value ...] */}}

The first whitespace-delimited token is the prop name. All remaining tokens are key=value pairs:

Key Type Description
type string, enum, bool, url, length Data type of the prop value. url is resolved before the template runs — see below. length must be a CSS length with a unit (600px, 80%, 40rem).
values comma-separated Valid values (for type=enum)
default any string, including "" Declares the prop optional and documents its fallback. Omit to make the prop required.
description text Prose description of the prop

Required and optional props. A prop is required if and only if it declares no default=. Use default="" for an optional prop with no fallback value:

{{/* braised:prop: src type=string description="Image path." */}}                    ← required
{{/* braised:prop: caption type=string default="" description="Caption text." */}}  ← optional, empty when unset
{{/* braised:prop: align type=enum values=left,center,right default=center */}}     ← optional

Props are validated when a page builds. Each of these is a build error; the block renders nothing (and is dropped from its parent's .ChildBlocks), and the build continues:

  • a required prop is absent from the opening fence
  • a type=enum prop has a value not listed in values=
  • a type=bool prop has a value other than true or false
  • a type=length prop is not a CSS length with a unit (px, %, rem, em, ch, vw, vh) or 0

A prop explicitly set to an empty string (caption="") counts as set. Props the schema does not declare are not checked, so a template with no markers accepts anything.

The default is not injected into .Props — braised validates against it but your template still applies the fallback, typically with {{index .Props "align" | default "center"}}. Keep the two in sync.

The old required= key is no longer supported. A marker that still uses it prints a warning at startup and is otherwise ignored.

URL props. Declare any prop that holds a link or media source as type=url:

{{/* braised:prop: href type=url description="Card link target." */}}
<a class="card" href="{{index .Props "href"}}">{{.Children}}</a>

Braised resolves the value before your template executes, exactly as it resolves a Markdown link destination, so .Props.href is already the final URL. Authors can then write any link form:

Author writes Template receives (with base_url: /docs)
href="/guide/install.md" /docs/guide/install/
href="braised:ref/install-guide" /docs/guide/install/
href="braised:static/files/guide.pdf" /docs/files/guide.pdf
href="https://example.com" https://example.com

Resolved values are validated like links: an unknown braised:ref/ or a missing braised:static/ file is a build error, and a path that matches no page or static file is a warning. Without type=url, a prop is an opaque string — it gets no base_url prefix and breaks on sub-path deployments.

Parents that read .ChildBlocks see the resolved values too.

Quoting rule: Values that contain spaces or = must be wrapped in double quotes. Unquoted values are delimited by the next whitespace character.

{{/* braised:prop: title type=string description="Card heading text." */}}
{{/* braised:prop: theme type=enum values=light,dark default=light description="Visual theme." */}}
{{/* braised:prop: count type=string default=3 */}}

:::callout{type="warning" title="Quoting is required for descriptions with spaces"} Forgetting quotes around a description silently truncates it. The scanner reads values up to the first space, so:

{{/* braised:prop: columns description=Number of grid columns. */}}

produces description=Number, discarding of grid columns.. No error or warning is emitted. Always quote descriptions that contain spaces:

{{/* braised:prop: columns description="Number of grid columns." */}}