On this page
5 Markdoc's key concepts in practice
5.1 Tags: extending content with custom components
Tags are Markdoc's primary extension to Markdown. They use a {% tagname %} syntax to create rich content components that go beyond what standard Markdown offers. Here is a safety callout as it would appear in a Manifest document:
warningAll personnel must complete DP familiarization training before standing watch. Refer to IMCA C 014 for minimum competency requirements.
This renders as a visually distinct warning box, but the important thing is what happens in the AST. The parser creates a node of type callout with an attribute type set to "warning" and child nodes containing the text. A schema definition controls what types are valid (perhaps warning, info, caution, note) and what content the callout may contain. If someone writes type="urgent" and that is not in the schema, validation catches it before publication.
Manifest uses custom tags like {% table %} for structured data tables, {% diagram %} for technical diagrams, and {% callout %} for highlighted notices. Each tag has a schema defining its rules, including what attributes it accepts, what child content is permitted, and how it renders.
5.2 Nodes: the Markdown you already know
Nodes represent standard Markdown elements such as headings, paragraphs, bold text, italic text, links, images, lists, and code blocks. The important detail is that Markdoc lets you customize node behavior through schemas without changing the authoring syntax. Writers use standard Markdown; the system applies additional structure behind the scenes.
For example, every heading in a Manifest document automatically receives a unique identifier, making it linkable and addressable. The writer simply types # Emergency Procedures and the system handles the rest.
5.3 Variables and functions: dynamic content without code
Variables inject values into content at render time. They use a $ prefix and must contain simple, serializable data (strings, numbers, booleans, arrays).
This procedure applies to vessels operating under the registry and was last reviewed on .
When this content is rendered for a Bahamas-flagged vessel, $flagState becomes "Bahamas" and $lastReviewDate becomes the actual date. The same content source serves every vessel in the fleet; only the variables change. No one copies and pastes the procedure for each vessel. No inconsistencies creep in.
Functions provide simple computed values (formatting a date, converting units, checking conditions) without introducing arbitrary programming logic into the content.
5.4 Partials: content reuse without copy-paste
Partials let you include content from one file inside another:
The referenced file is maintained in one place. Every document that includes it gets the current version automatically. When the standard safety warning changes, you update one file and every document reflects the change. This is the antidote to copy-paste drift across a fleet of vessels and a library of procedures.
5.5 Schema validation: automated quality control
Every tag and node in Markdoc can have a schema that defines its rules. A schema for a procedure tag might require a revision attribute, a regulatoryBasis attribute, and at least one child section. If a writer creates a procedure without specifying its regulatory basis, validation returns an error with the exact line number, before the document ever reaches publication.
const errors = Markdoc.validate(ast, config);
// Returns: [{ level: "error", message: "Missing required attribute: regulatoryBasis",
// location: { start: { line: 14 } } }]
This is automated compliance checking at the content level. Rather than relying on a human reviewer to notice a missing field in a 200-page manual, the system catches it instantly.