ER DIAGRAMS THAT DO NOT GO STALE
14. Januar 2026
Documentation that can't be verified automatically isn't documentation - it's a claim. A pattern against diagrams going stale.
The Starting Point
A schema diagram is documentation with an expiry date — except the date is never printed on it. It is correct the day it is made, and from then on it is an open question.
That is not a discipline problem. It is a property of the tooling: a diagram whose generation requires more than the repository itself cannot be built into the normal development flow. We looked at that closely while working on our own tooling, and erdify came out of it.
The tool is the smaller half of the story. The interesting part is the mechanism behind it, because it applies just as much to API documentation, architecture diagrams and onboarding guides.
Why diagrams go stale
The reason is structural.
Every established ERD generator needs one of two things: a live database connection or an importable application. Both are fine on a developer laptop. Both are awkward everywhere the diagram would actually be refreshed automatically:
- A CI job has no database. Spinning one up just to draw a picture is out of proportion.
- A documentation pipeline should not have to install your entire dependency tree.
- Anyone reviewing a repository they cannot install gets nothing at all.
So regeneration stays a manual step. And a manual step that blocks nothing competes permanently with everything else on the board. The problem is not the team; it is that updating the diagram was never in the path of any commit.
The diagram as a build artifact
The fix is to change what the diagram is derived from. Not from a database and not from a running application, but from the model source files — the very files the change itself touches.
That single move changes the economics. If a diagram can be produced by anything that can read the repository, then it can be produced by CI, by a pre-commit hook, by a docs build. And what can be produced automatically can also be verified automatically.
That is exactly what we built erdify for. It parses model files with Python’s standard-library ast module — it never imports your code and never opens a connection, which is why it has no runtime dependencies at all:
uvx erdify ./src/database -o docs/erd.pumlNo setup, no installing it into the project, no database. The tool reads files, nothing more.
The drift check in CI
The generator is replaceable. The pattern is not:
erdify ./src/database -o docs/erd.puml --check
--check regenerates the diagram in memory, compares it to the committed file, and exits with an error code if the two have diverged. Nothing is written and nothing is pushed.
Put that in CI and a pull request that changes a schema without bringing its diagram along fails. The developer regenerates, commits, and the diagram lands in the same pull request as the migration that caused it — where a reviewer can see both together.
That last point is the one that matters. The obvious alternative — a bot that regenerates the diagram and commits it to the default branch — feels more convenient, but it only moves the problem: the change lands on the main branch unreviewed, and if a run ever comes up empty it replaces a good diagram with a blank one. Silently, with a green check.
For a diagram embedded directly in a Markdown file — which is where it actually gets read — the same gate works:
erdify ./src/database --inject README.md --check--inject writes the diagram between two marker comments and leaves the rest of the file alone. As Mermaid, which GitHub and GitLab render natively, so the diagram sits in the pull request rather than in an attachment.
If you would rather catch it before the push, there is a hook:
# .pre-commit-config.yaml
repos:
- repo: https://github.com/devsuit-berlin/erdify
rev: v0.14.0
hooks:
- id: erdify-check
args: [./src/database, -o, docs/erd.puml]Naming the limit
Reading source instead of importing it has a price, and any honest write-up names it: the tool only sees what is written in the file. A model assembled at runtime via type(), a table name from a computed expression, fields attached in a loop — all invisible.
For most codebases this is a non-issue; for some it is disqualifying. If your schema genuinely comes into existence at runtime, an import-based generator is the right instrument. We say so in our comparison of the alternatives, including the cases where a competing tool is the better answer.
We think it is that openness that makes the rest dependable. Every architectural decision comes with the question of where it stops holding.
The polyglot case
Where this gets genuinely interesting is in systems that have grown over time. A Django backend, a FastAPI service on SQLModel, an internal tool built on plain dataclasses, plus a schema.sql for reporting — combinations like that accrete over years, each for good reasons at the time. The result is still four schemas, four tools and four diagram styles.
Reading source rather than runtime makes that tractable, because the parser does not have to load four incompatible frameworks. One command produces one diagram format across all of them — PlantUML, Mermaid, JSON, or a self-contained HTML page.
For teams on django-ninja there is now --base-classes: base classes defined outside the scanned files, such as ninja.Schema, can be named and are then recognised. And .sql files are read directly through the optional erdify[sql] extra — the analytics schema ends up in the same diagram as the ORM models.
Conclusion
You do not need our tool to take the point. If there is a schema diagram in one of your repositories, ask yourself three questions:
- What is it derived from? If the source is a single workstation, its accuracy depends on one person being available.
- Can CI reproduce it? If not, it will drift, and no amount of discipline prevents that.
- Does anything fail when it drifts? If not, the drift is invisible — and only the invisible kind hurts.
It is the same pattern as in Basic Server Security: the best lock is worthless if nobody checks that it still holds. Documentation that cannot be verified is not documentation. It is a claim.
TL;DR
- Schema diagrams go stale because generating them needs a database or an installed application — so it never runs automatically.
- Derive the diagram from the source files instead. Then anything that can read the repo can produce it.
- Producible means checkable:
--checkfails CI when diagram and models have diverged. - A gate in the pull request beats a bot commit on the main branch — the diagram belongs in the review that caused it.
- The trade-off: an AST parser only sees what is in the source. Models built at runtime are invisible to it.
uvx erdify ./src/database -o docs/erd.puml # erzeugen
erdify ./src/database -o docs/erd.puml --check # in CI prüfenerdify is open source under the MIT licence, developed and maintained by devsuit. Documentation: erdify.devsuit.io · Source: github.com/devsuit-berlin/erdify
erd-diagrams
Database modelling
Documentation
CI/CD
Python
Django
FastAPI
Open Source
Development process
Code quality
Fabian Clemenz
[email protected]

