Diagrams in Markdown and on GitHub
How a diagram fence in a Markdown file becomes a picture that GitHub displays, and how to keep it current. It is for anyone adding diagrams to a README or a doc.
- GitHub
- GitLab
TL;DR
| Rule | Yes | No |
|---|---|---|
| Source | Write a diagram fence and have your agent sync the file with sync_markdown | Paste SVG by hand |
| Pictures | Commit the generated .svg and .dark.svg files | Leave them out of git; the page would show broken images |
| Freshness | Run npx @buun_group/plotwork sync --check in CI; see Keep diagrams in sync in CI | Trust that nobody forgot to sync |
| Edits | Change the fence, then sync | Edit the generated block between the markers |
| Paths | Keep the pictures in assets/diagrams next to the document, or choose a folder with assetsDir | Move the files without syncing again |
1. What GitHub does and does not render
GitHub draws Mermaid fences itself, in the page. It does not draw custom fences, and it will not run a renderer for them. Plotwork therefore renders before commit: the picture is a file in the repository, and the Markdown refers to it.
flow "From fence to picture" desc="An author writes a diagram fence, sync renders it to SVG files and a picture block, and GitHub shows the picture."
node author person "Author or agent" sub="Writes a diagram fence"node sync function "sync_markdown" sub="Parses, lays out, renders"node svgs storage "SVG files" sub="Light and dark, committed"node block step "Picture block" sub="Inserted under the fence"node github terminal "GitHub page" icon=status:success
author -> sync : "Markdown file"sync -> svgs : "writes" role=datasync -> block : "writes" role=datasvgs -> github : "loaded by"block -> github : "references"Nodes:
- Author or agent (Writes a diagram fence), person
- sync_markdown (Parses, lays out, renders), function
- SVG files (Light and dark, committed), storage
- Picture block (Inserted under the fence), step
- GitHub page, terminal
Connections:
- Author or agent to sync_markdown: Markdown file
- sync_markdown to SVG files (stored data line): writes
- sync_markdown to Picture block (stored data line): writes
- SVG files to GitHub page: loaded by
- Picture block to GitHub page: references
2. What a sync produces
For each top-level diagram fence, a sync makes four changes. sync_markdown reads and writes no file itself: it returns the rewritten document and the SVG files, and the caller, usually your agent, saves them.
| Change | Where | Purpose |
|---|---|---|
| A collapsed "Diagram source" section | Around the fence | Readers see the picture first and can open the source. |
| A picture block | Directly under the section | An HTML picture element: the light SVG by default, the dark SVG when the reader's color scheme is dark. |
| A collapsed "Text description" | Inside the picture block | The diagram as plain text, so it can be read without the image and by tools that skip images. |
| Two SVG files | assets/diagrams/<id>.svg and <id>.dark.svg next to the document | The pictures. The id comes from the diagram title. |
The picture block starts and ends with marker comments and carries a hash. The hash covers the source and the rendered output, so a change to the diagram or to the renderer makes the block stale and the next sync rewrites it. Do not edit between the markers.
A fence that is indented, for example inside a list item, is still rendered but is not wrapped in the collapsed section.
A picture written by a sync carries its diagram source in its metadata, so read_diagram can give the source back from the picture alone. See Editing an existing diagram.
3. Running a sync
Ask your agent: "Render the diagrams in README.md". The agent sends the whole file and its path to sync_markdown, then writes the markdown it gets back over the file and each entry of files at its path. The local server does this with no key and no account. The same operation is POST /v1/markdown/sync over HTTP, which needs an API key and the Pro plan; the quick start has a request you can run.
| Input | What it does |
|---|---|
markdown and documentPath | The whole document as it is on disk, and where it lives, relative to the repository root with forward slashes. Picture links and file paths are worked out from the path. |
mode | sync, the default, returns the rewritten document and the files to write. check changes nothing and reports whether a picture is stale. |
assetsDir | The folder for the SVG files, relative to the repository root. The default is assets/diagrams next to the document. |
prefix | Starts every picture file name with this text. Use it when several documents share one asset folder. |
convertMermaid | When true, every Mermaid fence that can be converted becomes a diagram fence first. See Migrating from Mermaid. |
A diagram with an error is a normal result, not a failure: its fence is left as it was, the report names its line and column inside the document with a hint, and the other fences still sync. The limits on the size of a document are in the tool reference.
4. Where the pictures show up
| Place | Expected | Note |
|---|---|---|
| GitHub README and Markdown files | Yes | GitHub documents the picture element with a color-scheme source for light and dark images. |
| Editor preview (VS Code and similar) | Yes | Relative image paths load in the preview. |
| npm package page | Uncertain | npm may refuse to show SVG images in a README. |
| MCP (Model Context Protocol) clients | No | A client receives text and cannot show an SVG. Use outline_diagram for the text form. |
| GitLab and other hosts | Probably | They need to allow SVG in an image tag. |
5. Check your page after you push
| Check | What to look for |
|---|---|
| Light mode | The light picture loads and the text is crisp. |
| Dark mode | Switching GitHub to a dark theme loads the dark picture, with readable text and visible lines. |
| Phone width | The picture scales down to the screen and does not scroll sideways. |
| Collapsed sections | "Diagram source" and "Text description" open and show their content. |
| Nested folders | A picture in a subfolder loads; its path goes up to the shared assets folder, for example ../assets/diagrams. |
| Alt text | Hovering or using a screen reader gives the diagram's summary. |
Most failures come from a path that points outside the repository or from a picture file that was not committed.
6. Troubleshooting
| Symptom | Cause | Fix |
|---|---|---|
| The page shows a broken image | The SVG is not committed, or the path moved | Sync the file again, then commit the SVG files next to the document. |
A check with mode set to check says a block is stale | The fence or the renderer changed | Sync the file again and commit the result. |
| An SVG is left over after a diagram was renamed or removed | A sync returns pictures and never deletes old ones | Delete the unused files. |
| The fence shows as plain code on GitHub | The file was never synced | Sync it, write the result, and commit. |
| Two documents show each other's picture | Same title in a shared asset folder | Give each document its own prefix. |
See also
| Doc | Why |
|---|---|
| Writing diagrams | The language by example |
| Writing diagrams with an agent | The validate and fix loop |
| How a diagram is drawn | How source becomes SVG |