A typed templating DSL for generating Markdown.
The .emd format is designed for LLM orchestration, agent workflows, and documentation generation.
- Type-safe templates - Declare typed props with validation at render time
- Template composition - Include and compose templates with prop passing
- Control flow - Conditionals, loops, and expressions with proper operator precedence
- Plugin system - Extend with custom formatters via git-hosted plugins
- Type generation - Generate Python (Pydantic) or TypeScript types from templates
# Install
go install github.com/xray/ExpressiveMD/cmd/emd@latest
# Or clone and build
git clone https://github.com/xray/ExpressiveMD.git
cd ExpressiveMD
make buildCreate a template (hello.emd):
---
inbound name: string
inbound greeting: string defaults to "Hello"
inbound items: list[string]?
---
# ${greeting}, ${name}!
${if items}
Your items:
${for item in items}
- ${item}
${end}
${end}
Create props (props.json):
{
"name": "World",
"items": ["apple", "banana", "cherry"]
}Render:
emd build hello.emd --props props.jsonOutput:
# Hello, World!
Your items:
- apple
- banana
- cherryemd build <template.emd> --props <data.json> [--out <file>]
emd validate <template.emd> --props <data.json>
emd typegen --lang <python|typescript> --out <dir> <template.emd>
emd plugin install <git-url> [--ref <tag>]
emd plugin list
emd plugin remove <name>
inbound username: string
inbound age: int
inbound price: float
inbound active: bool
inbound email: string? # optional (null if not provided)
inbound limit: int defaults to 10 # with default value
inbound tags: list[string]
inbound user: map{name: string, email: string?, active: bool}
inbound items: list[map{id: int, label: string}]
${username}
${user.name}
${items[0].label}
${count + 1}
${name | uppercase()}
${if user.active && !user.banned}
Welcome, ${user.name}!
${else if user.pending}
Account pending approval.
${else}
Access denied.
${end}
${for item, idx in items}
${idx + 1}. ${item.name}
${end}
| Formatter | Description |
|---|---|
uppercase() |
Convert to uppercase |
lowercase() |
Convert to lowercase |
capitalize() |
Capitalize first letter |
trim() |
Remove leading/trailing whitespace |
length() |
Get length of string/list |
default(val) |
Fallback if null |
Formatters chain: ${name | trim() | uppercase()}
---
include header: ./partials/header.emd
include userCard: ./components/user-card.emd
---
${header(title="My Page")}
${for user in users}
${userCard(name=user.name, email=user.email)}
${end}
Generate typed bindings for your templates:
# Python (Pydantic v2)
emd typegen --lang python --out ./types template.emd
# TypeScript
emd typegen --lang typescript --out ./types template.emdInstall formatters from git repositories:
emd plugin install https://github.com/expressivemd/date-formatter --ref v1.0.0Use in templates:
---
plugin dateFormat: @expressivemd/date-formatter
---
Published: ${post.date | dateFormat("MMM D, YYYY")}
make build # Build binary to ./bin/emd
make test # Run tests
make install # Install to /usr/local/bin
make release # Cross-compile for all platformsThis is free and unencumbered software released into the public domain. See UNLICENSE for details.