Validate code blocks in your Markdown and MDX documentation.
Documentation · API Reference · Changelog
A CLI tool and Go library that validates code blocks embedded in Markdown and MDX documentation files. It catches syntax errors in your documentation before users do, using multiple parsing strategies to handle the partial code snippets commonly found in technical docs.
Supports Go (built-in parser), TypeScript, TSX, Rust, Nix, HCL/Terraform, and Templ (all via pure-Go tree-sitter, no CGO required).
Documentation rots. Code examples that compiled when written break silently after refactoring. Most projects have no way to catch this until a user files a bug report.
md-go-validator runs in CI and validates every code block in your .md and .mdx files. It understands that documentation snippets are rarely complete programs, so it tries multiple parsing strategies, from complete-file parsing down to wrapping fragments as expressions or statements.
| Feature | markdownlint | prettier | md-go-validator |
|---|---|---|---|
| Validates code block syntax | ✓ | ||
| Go parser (multi-strategy) | ✓ | ||
| Tree-sitter (7 languages) | ✓ | ||
| Pure Go (no CGO) | ✓ | ||
| CI exit codes | ✓ | ✓ | ✓ |
| JSON/SARIF output | ✓ | ||
| Baseline regression mode | ✓ | ||
| Skip directives | ✓ | ✓ |
- Extract code blocks from Markdown/MDX files (recursive directory scanning)
- Route each block to the correct language validator via the registry
- Validate Go blocks through 6 progressive parsing strategies (complete file, package wrapper, function wrapper, expression, statements, imports+statements)
- Validate other languages via embedded pure-Go tree-sitter parsers
- Report results in table, JSON, YAML, CSV, Markdown, SARIF, or quiet format
go install github.com/larsartmann/md-go-validator@latestOr use the GitHub Action in your workflow (no install needed):
- uses: LarsArtmann/md-go-validator@v1
with:
path: .# Validate all Go code blocks in the current directory
md-go-validator .
# Validate multiple languages
md-go-validator -l go,typescript,rust .
# JSON output for CI integration
md-go-validator -f json -o results.json .
# Pipe markdown via stdin
cat README.md | md-go-validator -| Option | Description |
|---|---|
-v, --verbose |
Show progress for each code block |
-q, --quiet |
Only show summary (no code in errors) |
-l, --language |
Comma-separated list of languages to validate |
-f, --format |
Output format (table, json, markdown, yaml, csv, quiet, sarif) |
--color |
Color mode (auto, always, never) |
-o, --output |
Write output to file |
-t, --timeout |
Timeout for validation (e.g. 30s, 5m) |
--exclude |
Glob pattern to exclude (repeatable, supports **) |
--skip-directive |
Custom skip directive (repeatable) |
--init |
Create a default .md-go-validator.yaml config file |
--baseline |
Baseline file of known errors; only new errors fail |
--save-baseline |
Generate baseline file from current run's errors |
--config |
Path to config file (default: auto-discover in CWD) |
--list-languages |
List all supported languages |
--fail-on-skipped |
Exit 1 if any blocks are skipped |
Create a .md-go-validator.yaml in your project root:
languages:
- go
- typescript
exclude:
- "vendor/*"
skipDirectives:
- "<!-- sketch -->"
format: tableRun md-go-validator --init to scaffold a default config file. CLI flags override config file values.
| Language | Identifier(s) | Parser |
|---|---|---|
| Go | go, golang |
Built-in (6-strategy) |
| TypeScript | typescript, ts |
Tree-sitter |
| TSX | tsx |
Tree-sitter |
| Rust | rust, rs |
Tree-sitter |
| Nix | nix |
Tree-sitter |
| HCL/Terraform | hcl, terraform, tf |
Tree-sitter |
| Templ | templ |
Tree-sitter |
All parsers are embedded in the binary. No external tools required.
The Go validator tries multiple approaches to handle partial code snippets:
- Pre-processing — Normalize documentation idioms (
{ ... }to{}, drop ellipsis-only lines) - Pseudo go.mod detection — Skip module directives (
require,replace,module) - Complete File — Parse as-is
- Package Wrapper — Wrap in
package main - Function Wrapper — Wrap in
func main() { ... } - Expression — Wrap as
_ = <code> - Statements — Wrap as function body
- Imports + Statements — Split import block from statements (the dominant docs pattern)
Error reporting uses best-attempt selection (the error from the strategy that parsed furthest).
For intentionally incomplete code snippets, place these before the code block or inside it:
<!-- skip-validate -->
```go
type MyStruct struct {
Name string
}
```<!-- skip-validate --><!-- skip-md-validate --><!-- md-skip --><!-- no-validate -->// skip-validate
A directive before a block must be a standalone line — prose that merely mentions a directive does not skip the next block. Inside a block, a directive anywhere in the content skips it.
md-go-validator -f json . # JSON for CI integration
md-go-validator -f sarif . # SARIF for GitHub Code Scanning
md-go-validator -f yaml . # YAML
md-go-validator -f csv . # CSV for spreadsheets
md-go-validator -f markdown . # Markdown tableThe JSON output format is documented with a JSON Schema. Each error entry includes an errorCode field (syntax, not_available, not_registered, or unknown) for programmatic error classification.
| Code | Meaning | When |
|---|---|---|
0 |
Success | All code blocks valid or skipped |
1 |
Validation errors | One or more code blocks have syntax errors |
2 |
Tool error | File not found, bad flags, I/O errors |
package main
import (
"context"
"fmt"
mdgovalidator "github.com/larsartmann/md-go-validator/pkg"
"github.com/larsartmann/md-go-validator/pkg/languages"
)
func main() {
validator := mdgovalidator.New(true).
WithLanguages([]languages.Language{
languages.LangGo,
languages.LangTypeScript,
})
ctx := context.Background()
results, err := validator.ValidateFile(ctx, "README.md")
if err != nil {
panic(err)
}
for _, r := range results {
if r.HasError() {
fmt.Printf("Error at line %s: %v\n", r.LineNumber, r.Error)
}
}
}# .github/workflows/docs.yml
name: Validate Documentation
on: [push, pull_request]
jobs:
validate:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
- run: go install github.com/larsartmann/md-go-validator@latest
- run: md-go-validator -f json -o results.json .Or use the built-in GitHub Action:
- uses: LarsArtmann/md-go-validator@v1
with:
path: .
args: "-f json"| Dependency | Purpose |
|---|---|
| gotreesitter | Pure-Go tree-sitter for multi-language parsing |
| go-output | Multi-format output (JSON, YAML, CSV, SARIF, table) |
| go-finding | Neutral Finding type for SARIF/LSP/JSON interchange |
| go-faster/yaml | YAML config file parsing |
nix build .# # Build the package
nix flake check # Run all checks (format, build, test)
nix develop # Enter dev shell
GOEXPERIMENT=jsonv2 go test ./... # Run tests
GOEXPERIMENT=jsonv2 golangci-lint run ./... # LintThe
GOEXPERIMENT=jsonv2prefix is required becausego-outputimports the experimentalencoding/json/v2package. The nix devShell sets it automatically; outside nix, export it manually or usedirenv allowwith the provided.envrc. Without it,go buildandgo testfail withbuild constraints exclude all Go files in .../encoding/json/v2.
MIT License. See LICENSE for details.