Format SDK

Frontmatter in Formats

Reading, validating, and typing frontmatter with the Format SDK.

Frontmatter is YAML metadata between --- fences at the top of a Markdown file. The Format SDK makes it fully typed and validated.

Reading frontmatter

When you call parse(content), frontmatter is extracted and parsed automatically. In a format definition, access it via doc.frontmatter:

import { datamark } from "datamark";

const MyFormat = datamark({
  parse(doc) {
    const title = doc.frontmatter?.title as string; // Record<string, unknown> — needs cast
    return { title: title ?? "" };
  },
});

Without a schema, doc.frontmatter is Record<string, unknown> | null. Provide a frontmatterSchema and it becomes fully typed.

Validating frontmatter

Provide a frontmatterSchema and doc.frontmatter becomes typed:

import { datamark } from "datamark";
import * as z from "zod";

const FrontmatterSchema = z.object({
  version: z.string(),
  author: z.string(),
});

const DataSchema = z.object({
  version: z.string(),
  author: z.string(),
  title: z.string(),
});

const FrontmatterFormat = datamark({
  frontmatterSchema: FrontmatterSchema,
  schema: DataSchema,
  parse(doc) {
    const fm = doc.frontmatter;
    const h1 = doc.root.children.find((n) => n.type === "section") as any;
    const title = h1 ? (h1.heading.children[0] as any)?.value ?? "" : "";
    return { version: fm.version, author: fm.author, title };
  },
});

const frontmatterMarkdown = `---
version: 1.0.0
author: Ada
---

# Release Notes`;
const result = FrontmatterFormat.parse(frontmatterMarkdown);
console.log(result.version); // "1.0.0"

frontmatterSchema is validated before your parse function runs. If the document lacks frontmatter and your schema does not allow null, parse() throws ValidationError immediately. If you need frontmatter to be optional, use an optional schema such as z.object({ ... }).optional().

Pattern: frontmatter-only formats

Some formats use frontmatter as the primary data source and treat the body as secondary:

import { parse, datamark } from "datamark";
import { textContent, inlineText } from "datamark/parse";
import { heading, paragraph } from "datamark/stringify";
import * as z from "zod";

// Pattern: frontmatter-only format
const ConfigFormat = datamark({
  frontmatterSchema: z.object({
    name: z.string(),
    version: z.string(),
    features: z.array(z.string()),
  }),
  schema: z.object({
    config: z.object({
      name: z.string(),
      version: z.string(),
      features: z.array(z.string()),
    }),
    readme: z.string().optional(),
  }),
  parse(doc) {
    const config = doc.frontmatter;
    const readme = textContent(doc.root).trim() || undefined;
    return { config, readme };
  },
});

// Pattern: optional frontmatter
const OptionalFrontmatterFormat = datamark({
  frontmatterSchema: z.object({ title: z.string() }).nullable(),
  schema: z.object({ title: z.string() }),
  parse(doc) {
    const title =
      doc.frontmatter?.title ??
      inlineText(
        (
          doc.root.children.find((n) => n.type === "section") as any
        )?.heading?.children ?? []
      );
    return { title };
  },
  stringify(data) {
    return heading(data.title) + "\n\n" + paragraph("Body content.") + "\n";
  },
});

// Sample documents
const configDoc = `---
name: my-app
version: "1.0.0"
features:
  - auth
  - api
---

This app does things.
`;

const titledDoc = `---
title: Hello World
---

Body here.
`;

const untitledDoc = `# Fallback Title\n\nBody here.`;
const result = ConfigFormat.parse(configDoc);
console.log(result.config.name);     // "my-app"
console.log(result.config.version);  // "1.0.0"
console.log(result.readme);          // "This app does things."

Pattern: optional frontmatter

Not every document has frontmatter. Handle it gracefully with a nullable schema:

const MyFormat = datamark({
  frontmatterSchema: z.object({ title: z.string() }).nullable(),
  parse(doc) {
    const title = doc.frontmatter?.title
      ?? inlineText((doc.root.children.find(n => n.type === "section") as any)?.heading?.children ?? []);
    return { title };
  },
});

Error handling

If the YAML itself is malformed, parse() throws FrontmatterError before your format runs:

import { parse } from "datamark";
import { FrontmatterError } from "datamark/parse";

try {
  parse("---\nbad: [\n---\nBody");
} catch (err) {
  if (err instanceof FrontmatterError) {
    console.error("YAML is broken:", err.message);
  }
}

If the YAML is valid but does not match your frontmatterSchema, you get ValidationError with structured issue data:

import { ValidationError } from "datamark/parse";

try {
  BlogFormat.parse("---\ntitle: 123\n---\n# Hello"); // title should be string
} catch (err) {
  if (err instanceof ValidationError) {
    console.log(err.issues);
    // [{ message: "Expected string, received number", path: ["title"] }]
  }
}

On this page