Data-driven PlantUML — generating diagrams from a database or API

puml.online

Use PlantUML + a few lines of script, so your diagrams always follow reality.

Why data-driven?

The most common embarrassment with business diagrams:

  • A column gets added to the DB; the ER diagram forgets to follow, and people join on memory.
  • A service dependency changes; the deployment diagram is the one from three months ago.
  • A teammate moves to a new team; the org chart still shows them on the old one.
  • Every week someone manually redraws — miss one week and someone cites the wrong link.

Idea: the “facts” of a diagram live in code / DB / API docs / config — and those are the source of truth. So let the diagram be generated from that truth. That approach is called a data-driven diagram.

A concrete example: service dependency diagram

Suppose we have a k8s namespace where every Deployment has a label app=xxx. We want to draw app=a → app=b dependencies.

Step 1: extract data from kubectl

1
2
3
4
5
6
kubectl get deployments -n prod -o json | jq '
[.items[] | {
name: .metadata.labels.app,
depends: (.metadata.annotations."depends-on" // "")]
}] | map(select(.name))
' > deps.json

Output looks like:

1
2
3
4
5
[
{"name": "frontend", "depends": "backend,redis"},
{"name": "backend", "depends": "postgres,redis"},
{"name": "redis", "depends": ""}
]

Step 2: a Node script that emits puml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
// scripts/gen-deps.js
import fs from "node:fs";
import { execSync } from "node:child_process";

const deps = JSON.parse(
execSync('kubectl get deployments -n prod -o json')
.toString()
.match(/\[.*\]/s)?.[0] ?? "[]"
);

const groups = {};
for (const d of deps) {
const layer = d.name.startsWith("db") ? "storage" :
d.name.startsWith("cache") ? "cache" : "app";
(groups[layer] ??= []).push(d.name);
}

let puml = "@startuml deps\nskinparam nodesep 30\nskinparam ranksep 40\n\n";
for (const [layer, names] of Object.entries(groups)) {
puml += `package "${layer}" {\n`;
for (const n of names) puml += ` component "${n}" as ${n}\n`;
puml += "}\n\n";
}
for (const d of deps) {
for (const dep of (d.depends || "").split(",").filter(Boolean)) {
puml += `${d.name} --> ${dep}\n`;
}
}
puml += "\n@enduml\n";

fs.writeFileSync("output/deps.puml", puml);
console.log("wrote deps.puml");

Step 3: render + commit

1
2
3
4
5
node scripts/gen-deps.js
# Use PlantUML CLI to render to SVG (or via plantuml.com / TeaVM)
plantuml -tsvg output/deps.puml -o output/
git add output/deps.svg
git commit -m "chore: update dependency diagram from prod"

Result: every CI run, the diagram is up to date.

A few common “data source → diagram” templates

Scenario Data source Output
Service deps k8s labels / apollo / consul component / package
DB ER pg_dump --schema-only entity / class
State machine XState / status table state diagram
Approval flow Lark / Worktile / Jira activity diagram
Org chart HR system / LDAP class diagram
API contract OpenAPI / Protobuf class diagram

A pure-bash DB ER extractor

1
2
3
4
5
pg_dump --schema-only -t "*" mydb \
| grep -E "CREATE TABLE|^ \"\\w+\"" \
| awk '/CREATE TABLE/ {table=$3; next} /\"/ {print table, $0}' \
> tables.tsv
# Then awk converts to entity/relationship puml, omitted

Integration tips

1. Output small data, not the raw DB

Compress SQL / kubectl / API output into flat JSON (like the deps.json above). The puml generator only reads JSON, never the DB.

Benefits:

  • The puml generator has zero dependencies, is pure text, easy to PR-review.
  • When the data source changes (e.g. swapping DB), only the data extractor changes — puml stays put.

2. Keep the puml generator in the repo

Commit scripts/gen-*.puml.js alongside package.json. Put a one-liner in README:

This diagram is auto-generated from live data. To change it, change the data source (k8s labels / DB schema).

3. Enforce render-verify in CI

After every build:

1
2
3
node scripts/gen-deps.js
plantuml -tsvg output/deps.puml -o output/
git diff --exit-code output/deps.svg || (echo "diagram stale" && exit 1)

If the data source changed but the puml script didn’t, the build fails — alerting humans to refresh the diagram.

Pitfalls I’ve hit

  • Cycles: A → B → A makes Graphviz error. Add if seen.has(a+b) continue to your generator.
  • Too many nodes: >100 components make SVG stall. Split into package blocks, paginate by layer.
  • CJK tofu: PlantUML SVG inlined on GitHub renders CJK as squares. Add skinparam defaultFontName "Noto Sans CJK SC" at the top of puml.
  • Sensitive data: dependency graphs from prod can include internal hostnames. Anonymise (hash or codename) before writing puml.

Recap

  • Diagrams should follow the truth — whoever owns the truth owns the source.
  • Split extract and generate-puml into separate scripts.
  • CI’s git diff --exit-code makes “diagram not updated” a compile error.

Next

  • Title: Data-driven PlantUML — generating diagrams from a database or API
  • Author: puml.online
  • Created at : 2026-07-28 16:36:00
  • Updated at : 2026-08-14 21:34:29
  • Link: https://puml.online/blog/plantuml-data-driven-en/
  • License: This work is licensed under CC BY-NC-SA 4.0.