You already built the app — your ORM models are the schema. orm2erd reads them and generates an
ERD (Entity-Relationship Diagram) for you, instead of you drawing and maintaining one by hand.
Status: early development — see the tables below for what's supported today vs. planned.
orm2erd scans your project, figures out which ORM you're using, and turns your existing
models/schema into diagram code — Mermaid, DBML, PlantUML, D2, and more. No manual diagramming,
no drift between your code and your docs.
detect ORM → resolve entry point(s) → parse/introspect → normalize to IR → emit diagram code(s) → write file(s)
| ORM | Status | |
|---|---|---|
| Prisma | ✅ Supported | |
| Sequelize | ✅ Supported | |
| Mongoose | ✅ Supported | |
| TypeORM | ✅ Supported | |
| Drizzle | ✅ Supported | |
| MikroORM | ✅ Supported | |
| BookShelf.js | 🚧 Planned | |
| Waterline | 🚧 Planned | |
| Objection.js | 🚧 Planned |
| Format | Status | |
|---|---|---|
| Mermaid | ✅ Supported | |
| DBML | ✅ Supported | |
| PlantUML | ✅ Supported | |
| D2 | ✅ Supported | |
| nomnoml | ✅ Supported | |
| QuickDBD | ✅ Supported | |
| Graphviz DOT | ✅ Supported | |
| Structurizr DSL | 🚧 Planned | |
| Pikchr | 🚧 Planned |
Node.js >= 24.
Run without installing (recommended — always gets the latest version):
npx orm2erd
# or npx orm2erd@latest to bypass a locally cached version
pnpm dlx orm2erd
# or pnpm dlx orm2erd@latest
bunx orm2erd
# or bunx orm2erd@latestOr install globally:
npm i -g orm2erd
orm2erdInteractive:
npx orm2erd┌ orm2erd
│
◇ Detected: prisma
◆ Entry point for prisma:
│ prisma/schema.prisma
◆ Output format(s):
│ mermaid
◆ Output path:
│ erd.mmd
◆ Type labels:
│ Canonical
│
◇ Written to erd.mmd
│
└ Done
erd.mmd:
erDiagram
%% Entities
User {
int id PK "default: autoincrement()"
string email UK
string? name
}
Post {
int id PK "default: autoincrement()"
string title
string? content
boolean published "default: false"
int authorId FK
}
Comment {
int id PK "default: autoincrement()"
string text
int postId FK
int authorId FK
}
Tag {
int id PK "default: autoincrement()"
string name UK
}
%% Relationships
User ||--o{ Post : "posts (authorId)"
User ||--o{ Comment : "comments (authorId)"
Post ||--o{ Comment : "comments (postId)"
Post }o--o{ Tag : "tags"
Non-interactive (CI-friendly):
npx orm2erd --orm prisma --entry ./prisma/schema.prisma --format mermaid,dbml --out ./erdYou can select multiple output formats in a single run — the schema is parsed once and reused
across every format you pick. --out accepts either a bare name (erd, gets each format's
extension appended) or a full filename (erd.md, used exactly as given when there's only one
output format).
By default, field types are emitted in a canonical, portable form (e.g. string, int). Pass
--type-mode native to emit the ORM's own type names instead (e.g. Prisma's String, Int):
npx orm2erd --orm prisma --entry ./prisma/schema.prisma --format mermaid --type-mode nativeCommit your generated ERD, then use --check to fail CI whenever the committed file no longer
matches what your current models would produce — so the diagram can never silently drift out of
date:
npx orm2erd --orm prisma --entry ./prisma/schema.prisma --format mermaid --out ./docs/erd.mmd --check- matches → prints
ERD up to dateand exits0 - differs → prints a diff of what changed and exits
1 - missing → tells you to generate it first and exits
1
--check never touches the filesystem, so it's safe in a pre-commit hook or a pull-request check.
It's flag-driven and non-interactive by design: pass it the same output-affecting flags you
generated with (--format, --out, and --type-mode if you use it), or it will report drift
against a differently-rendered file.
Drop it into a workflow:
# .github/workflows/erd.yml
name: ERD in sync
on: [pull_request]
jobs:
check:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- uses: actions/setup-node@v7
with:
node-version: 24
- run: npm ci
- run: npx orm2erd --orm sequelize --entry ./models/index.js --format mermaid --out ./docs/erd.mmd --checkNote: ORMs parsed statically from a schema file (e.g. Prisma) need nothing but that file in CI. ORMs introspected by importing your models require CI to be able to run them (install dependencies, and provide any env vars the models need at import time) — same as generating the ERD normally. See docs/adapters.md for how each ORM is parsed.
Same flow works for any supported ORM — just swap --orm and --entry. For
example, a Sequelize project with associations declared via hasMany/belongsTo:
npx orm2erd --orm sequelize --entry ./models/index.js --format mermaid --out ./erderd.mmd:
erDiagram
%% Entities
User {
int id PK
string email UK
string? name
datetime createdAt
datetime updatedAt
}
Post {
int id PK
string title
boolean? published "default: false"
datetime createdAt
datetime updatedAt
int? authorId FK
}
%% Relationships
User ||--o{ Post : "posts (authorId)"
| Flag | Description |
|---|---|
--orm <name> |
ORM to use — see Supported ORMs. Skips detection. |
--entry <path> |
Path to the ORM's schema/model entry. Skips the entry-point prompt. |
--format <formats> |
Output format(s), comma-separated, or all for every supported format — see Output formats. |
--out <path> |
Output path — bare name gets each format's extension appended; a full filename is used as-is when there's only one format. A directory (trailing slash, or an existing directory) writes erd.<ext> inside it. |
--type-mode <mode> |
Type labels to emit: canonical (portable, default) or native (ORM-specific). |
--check |
Verify the committed ERD file(s) are up to date instead of writing. Exits non-zero on drift or if a file is missing; writes nothing. See Keeping the ERD in sync. |
--stdout |
Print the diagram to stdout instead of writing a file — requires exactly one --format. Status output goes to stderr, so the diagram can be piped cleanly (e.g. orm2erd ... --stdout > erd.mmd or into another tool). |
--copy |
Copy the diagram to the clipboard instead of writing a file — requires exactly one --format. |
--verbose |
Show log output from the target codebase during extraction (suppressed by default). |
-v, --version |
Output the current version. |
-h, --help |
Show usage and examples. |
In a TTY, any flag you omit falls back to an interactive prompt. In CI (no TTY, or CI=true),
prompts are skipped — pass --orm, --entry, and --format explicitly, or the run exits with an
error telling you which one is missing.
For Prisma, if a prisma.config.* file is present, its schema field is respected as the entry
point's default candidate,
same as the Prisma CLI.
Diagrams drawn by hand go stale the moment the schema changes. Your ORM already has an accurate,
structured picture of your data model — orm2erd just reads that instead of asking you to
redraw it.
See CLAUDE.md for architecture, the adapter/emitter contract, and design decisions. For exactly how each ORM is detected and parsed, see docs/adapters.md. To report a security vulnerability, see SECURITY.md.
