Skip to content

Repository files navigation

md-tmpl: Strongly typed markdown templates

Fast and powerful templates. Valid markdown. Strongly typed and great for agents.

The Template

---
consts:
  - MODEL = str := "gemini-3.5-flash"
  - MAX_FINDINGS = int := 20

types:
  - Severity = enum(Critical, High, Medium, Low)
  - Verdict = enum(Approved, NeedsChanges(reason = str), Rejected(reason = str))

params:
  - reviewer = str
  - file_path = str
  - severity = Severity
  - findings = list(line = int, message = str, severity = Severity)
  - verdict = Verdict
---

# Code Review: {{ file_path }}

Reviewer: {{ reviewer }} · Model: {{ MODEL }}
Severity: {{ kind(severity) }} · Findings: {{ len(findings) }}/{{ MAX_FINDINGS }}

> {% for finding in findings %}

- **L{{ finding.line }}** ({{ kind(finding.severity) }}): {{ finding.message }}

> {% /for %}

> {% match verdict %}
> {% case Approved %}
**Approved** — ship it.

> {% case NeedsChanges %}

🔄 **Needs changes:** {{ verdict.reason }}

> {% case Rejected %}
**Rejected:** {{ verdict.reason }}

> {% /match %}

Types and fields are validated at build time — rendering happens at runtime:

use md_tmpl::include_template;

include_template!("prompts/code_review.tmpl.md");

let output = code_review::Params::builder()
    .reviewer("Alice")
    .file_path("src/main.rs")
    .severity(code_review::Severity::High)
    .findings([
        code_review::FindingsItem::builder()
            .line(42)
            .message("unused import")
            .severity(code_review::Severity::Low)
            .build(),
    ])
    .verdict(code_review::Verdict::NeedsChanges {
        reason: "remove dead imports".into(),
    })
    .build().render().unwrap();

Wrong types, missing fields, non-exhaustive matches — all caught at build time. Templates can also be loaded and validated at runtime for dynamic or hot-reload use cases.

Why?

Problem Solution
Prompts buried in code Markdown-native.tmpl.md files render in any editor or on GitHub
Errors found in production Strict typing — caught at build time (Rust) or render time
LLMs drift templates silently Agent-safe — the engine catches drift immediately
Syntax breaks in previews Markdown-safe — renders cleanly in any viewer, even unrendered

Features

Feature Description
Typed parameters str, int, float, bool, list(…), struct(…), enum(…), option(…), tmpl(…)
Type aliases types: defines reusable named types
Cross-template imports imports: pulls types via dotted paths (stem.TypeName)
Typed lists list(title = str, score = int) — iterate with {% for %}, fields validated
Enum dispatch match/case with exhaustiveness checking and field narrowing
Includes as links {% include [name](path.tmpl.md) with … %} — clickable, type-checked
Inline templates {% tmpl name %} — reusable fragments without separate files
Constants consts: for file-scoped immutable values
Environment variables env: for compile-time injection from the build environment
String interpolation {{ expr }} inside all quoted strings — conditions, includes, panic messages
Built-in functions idx(b), len(x), kind(x), kinds(t), has(x) + filters (upper, lower, trim, join, …)
Readable as markdown > {% %} blockquote prefix keeps control flow visually separated from prose
Markdown-safe syntax Valid YAML frontmatter, clean () type syntax — looks good even unrendered

Note: The > prefix is required only on {% %} tag lines — it is stripped before compilation. Content lines between tags are normal text and should not use > . A content line starting with > is kept verbatim as a literal markdown blockquote in the output.

Quick Start

use md_tmpl::include_template;

// Parsed + validated at build time — generates typed structs from frontmatter
include_template!("prompts/task_report.tmpl.md");

let output = task_report::Params::builder()
    .title("Sprint 42")
    .priority(task_report::Priority::Critical)
    .tasks([
        task_report::TasksItem::builder()
            .name("Fix auth bypass")
            .urgency(task_report::Priority::Critical)
            .build(),
        task_report::TasksItem::builder()
            .name("Update dependencies")
            .urgency(task_report::Priority::Low)
            .build(),
    ])
    .build().render().unwrap();

Performance

Built for speed — zero-allocation rendering in Rust, native FFI in all bindings.

Rust (render-only, pre-parsed)

Scenario md-tmpl Tera MiniJinja Handlebars
simple 164 ns 🏆 214 ns 548 ns 715 ns
loop 499 ns 🏆 637 ns 1.90 µs 3.32 µs
conditional 218 ns 🏆 369 ns 598 ns 1.39 µs
hero 2.13 µs 🏆 2.18 µs 7.58 µs 24.01 µs
mega 8.53 µs 🏆 10.63 µs 28.46 µs 90.35 µs

See the language-specific READMEs or the benchmarks suite for full details and methodology.

Language Bindings

First-class native packages across all major ecosystems — with tailored ergonomics and high-performance engines:

  • Rust — Zero-allocation rendering, build-time validation via proc macros (include_template!), ergonomic TypedBuilder & serde integration, ctx! macro, caching, and hot-reload.
  • Python — 4–8× faster than Jinja2. Auto-generated dataclasses, mypy/pyright static typing stubs, native Python 3.10+ structural pattern matching (match/case), and direct import hooks (from prompts.review import CodeReview).
  • Go — 3–6× faster than standard text/template. Zero-allocation FFI engine, idiomatic struct tag mapping (json:"field"), map rendering, and static enum typing via TaggedVariant.
  • TypeScript — Build-time TypeScript type generation (generateTypesFromFile), generic TypedTemplate<P> contracts, type-safe enum constructors (defineVariants), and exhaustive pattern matching (match).
  • WASM — Exact Rust-engine feature parity in Node.js and browsers (~200 KB .wasm). Supports zero-copy binary throughput (renderFlexbuffers) and implements the exact same ITemplate interface as pure TypeScript.

Full Reference

See SPEC.md for the complete syntax — all control-flow tags, filters, built-in functions, whitespace control, and error diagnostics.

Packages

License

Apache-2.0 OR MIT

About

Fast and powerful templates. Valid markdown. Strongly typed and great for agents. Supports Rust, Python, Go, TypeScript

Topics

Resources

Contributing

Stars

Watchers

Forks

Releases

Packages

Contributors

Languages