skip to content
Jason Worden
Table of Contents

You tweak a flowchart, fat-finger an edge, and push. The build is green. The diagram renders as a red Syntax error box — live, on the published page — and you don’t find out until someone screenshots it back to you.

Mermaid diagrams break quietly. They’re plain text in your Markdown, so nothing type-checks them. They only fail when something tries to render them, and by then they’ve already shipped. A broken unit test stops the line; a broken diagram sails straight through, because nothing in CI is looking at it.

That’s a shame, because diagrams in the repo are some of the best documentation you have — they render on GitHub, they sit right next to the code they describe, and they’re plain text that humans and coding agents can both read. Which is exactly why a broken one stings: it looks authoritative right up until it doesn’t render.

So I built mermaid-lint to make broken diagrams fail CI like any other test.

What it is

mermaid-lint validates Mermaid diagrams using the official mermaid.parse() API — the same parser your renderer uses, not a reimplemented approximation. If Mermaid can’t parse it, the check fails, with the file, line, and the actual error message.

What you get in the box:

  • Finds every diagram. Scans .md, .mdx, .markdown, and .mmd files, and extraction is indentation-aware — it catches diagrams nested inside blockquotes, list items, and indented sections, not just top-level fences.
  • Git-aware by default. Only checks tracked files (so a diagram you’re still drafting doesn’t fail CI until you commit it); --all scans the whole filesystem. Pass glob patterns like mermaid-lint "docs/**/*.md" "src/**/*.md" and the tool expands them itself — no shell-globbing surprises.
  • Built for CI and tooling. --format json gives machine-readable output for CI annotations, editor integrations, or custom pipelines. Colored human output respects NO_COLOR and non-TTY. You also get a diagram-type distribution summary — how many flowcharts, sequence diagrams, and so on live in your docs.
  • Pure Node. No headless browser, no Puppeteer, no CDN dependency. ESM-native, Node ≥ 20.

It ships as a small family of packages so you adopt exactly the layer you need:

The drop-in

Here’s the entire diagram-checking test file in this blog’s repo:

import { defineMermaidTests } from '@mermaid-lint/vitest'
defineMermaidTests()

That discovers every Mermaid block in every tracked Markdown file, runs the real parser over each one, and gives each diagram its own named test case — src/content/post/06-mermaid-lint.md:47 is valid. When something breaks, the report points straight at it. There’s no registry of diagrams to maintain: any new diagram in any tracked file is covered automatically.

Prefer Jest? Same call, from @mermaid-lint/jest. Not using a test runner at all?

Terminal window
npx @mermaid-lint/cli # check git-tracked files
npx @mermaid-lint/cli "docs/**/*.md" # or a glob
npx @mermaid-lint/cli --all --format json # whole tree, machine-readable
flowchart LR
  A["discover: git-tracked or glob"] --> B["extract (indentation-aware)"]
  B --> C["mermaid.parse(body)"]
  C -->|throws| FAIL["fail: file:line + message"]
  C -->|ok| PASS[pass]

A flowchart in Mermaid

How it compares

There are good tools in this space already. The two closest are suwa-sh/md-mermaid-lint and sammcj/mermaid-check. Here’s an honest read on where each fits.

mermaid-lintmd-mermaid-lintmermaid-check
Validation engineofficial mermaid.parse()official mermaid.parse()Go reimplementation (own AST)
In lockstep with what Mermaid renders❌ — separate parser, can drift
Deeper semantic checks (undefined refs, dup IDs)
Vitest / Jest adapters
Programmatic library API✅ (@mermaid-lint/core)✅ (Go)
CLI
File discoverygit-tracked by default, or globglob you passglob / extension
Indentation-aware extraction
JSON output✅ (grouped)
RuntimeNode, pure JSNodeGo binary

mermaid-check is the most thorough validator of the three — and I’ll say so plainly. It builds a full AST in Go and does semantic checks mermaid-lint doesn’t: undefined references, duplicate identifiers, type mismatches. It’s also extremely fast. The trade-off is that it’s a from-scratch reimplementation of the Mermaid grammar, so it can diverge from what Mermaid.js actually renders; it needs a Go toolchain; and it has no JavaScript test-runner integration. If you want the deepest possible validation and a Go binary fits your stack, it’s an excellent choice.

md-mermaid-lint uses the same engine mermaid-lint does — the real mermaid.parse() with jsdom. The difference is shape: it’s a CLI you hand globs to, and that’s it.

mermaid-lint’s niche is running the real parser while meeting a JavaScript project where it already lives:

  • Each diagram becomes a case in the Vitest or Jest run you already have — same reporter, same watch mode, same green check. A broken diagram fails for the same reason a broken unit test does, and shows up in the same place.
  • The core is a library with a typed validateBlock(), so unusual setups build on the primitives instead of fighting a CLI.
  • Git-aware discovery means there’s usually no glob config to maintain at all.

If your repo is JS/TS and your diagrams live in Markdown, that combination is the point.

What it won’t do

One limit, stated plainly: mermaid-lint validates syntax — that the diagram parses. It doesn’t validate meaning. A diagram that parses perfectly but describes a flow your code moved away from will pass, because no parser knows your architecture changed; that’s a code-review problem, not a linter one. And it stops at parsing, so it won’t flag the deeper semantic issues (undefined references, duplicate IDs) that mermaid-check will — if you need those, reach for that.

The post is a test case

This post has a Mermaid diagram in it. The moment it was committed, the check this post describes started running against it on every CI build — using mermaid-lint, against this blog’s own repo. If that diagram were malformed, this post would not have shipped.

That’s the whole point: the same discipline you apply to code starts applying to documentation. The real parser, one line of config, every tracked diagram covered for free — including this one.

If you keep diagrams in your repo, give it a try.