What is datamark?

A TypeScript library for parsing Markdown into typed objects and serializing them back using declarative formats.

datamark is a TypeScript library for turning Markdown documents into typed objects — and back again. It gives you a unified AST with a native section tree, plus a lightweight format system for defining how documents map to your data structures.

Why datamark?

Markdown is the universal format for structured text, but parsing it into typed data usually means one of two things:

  1. Regex and string manipulation — fragile, unreadable, unmaintainable.
  2. Abstract syntax trees — powerful, but you still write imperative traversal code.

datamark gives you a third option: a unified AST with a native section tree and a lightweight format system that makes common patterns trivial.

Bring your own validator. datamark uses the Standard Schema v1 interface, so Zod, Valibot, ArkType, TypeBox, and any compliant library work out of the box.

A 10-second demo

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

const PlanFrontmatterSchema = z.object({ id: z.string() });

const PlanSchema = z.object({
  id: z.string(),
  title: z.string(),
  steps: z.array(
    z.object({
      description: z.string(),
      scripts: z.array(z.string()),
    })
  ),
});

const PlanFormat = datamark({
  frontmatterSchema: PlanFrontmatterSchema,
  schema: PlanSchema,

  parse(doc) {
    const id = doc.frontmatter.id;
    const titleSection = doc.root.children.find((n) => n.type === "section") as any;
    const title = titleSection ? inlineText(titleSection.heading.children) : "";

    const steps = titleSection
      ? (titleSection.children.filter((n: any) => n.type === "section") as any[]).map(
          (section) => {
            const scripts = findAll(section, (n) => isCodeBlock(n, "javascript")).map(
              (n: any) => n.value
            );
            const description = textContent(section).trim();
            return { description, scripts };
          }
        )
      : [];

    return { id, title, steps };
  },

  stringify(data) {
    let md = frontmatter({ id: data.id }) + heading(data.title) + "\n\n";
    for (const step of data.steps) {
      md += heading("Step", 2) + "\n\n" + paragraph(step.description) + "\n\n";
      for (const script of step.scripts) {
        md += codeBlock(script, "javascript") + "\n\n";
      }
    }
    return md;
  },
});

const planMarkdown = `---
id: plan-001
---
# Q3 Roadmap

## Step

Set up the project.

\`\`\`javascript
npm init -y
\`\`\`

## Step

Implement the core features.`;
const result = PlanFormat.parse(planMarkdown);
console.log(result.id);     // "plan-001"
console.log(result.title);  // "Q3 Roadmap"

What datamark is NOT

It is not a static site generator. It parses and transforms Markdown, but it does not build HTML pages or apply themes.

  • It is not a Markdown renderer. It produces data, not HTML.
  • It does not stream. Input string in, typed object out.
  • It is not a general-purpose parser generator. It is specifically designed for Markdown documents.

Who is it for?

CLI & Script Developers

Parse READMEs, changelogs, plan files, and API docs into structured data for automation, validation, and CI.

Application Developers

Embed typed Markdown parsing into your app for user-generated content, configuration files, and documentation formats.

Quick navigation

GoalSection
New here?Quickstart
Understand the architectureThe AST
Look up a functionParse SDK
See real formatsExamples
Compare to alternativesComparisons

On this page