See your PostgreSQL database as a 3D force-directed graph. Tables become nodes, relationships become links, and the layout pushes tightly-connected tables together — so the shape of your schema becomes something you can actually look at and explore.
- Color = row count (log heatmap; empty tables are grey)
- Size = number of connections
- Link color = relationship strength — optionally verified against real data
- Click a table to highlight its neighbors and inspect its columns and keys
- Search, spacing control, and "hide unconnected tables" — all in one HTML file
The output is a single, self-contained HTML file. No server, no build step, no internet needed to view it. Open it in any browser, or share it as one file.
The repo ships with a pre-built demo of a synthetic e-commerce database — no database or setup required:
# just open it in your browser:
open dist/demo.html # macOS
start dist/demo.html # Windows
xdg-open dist/demo.html # LinuxDrag to rotate, scroll to zoom, click a table to inspect it.
pip install -r requirements.txtCopy .env.example to .env and fill in your database details:
cp .env.example .envDB_HOST=localhost
DB_PORT=5432
DB_NAME=my_database
DB_USER=readonly_user
DB_PASSWORD=your_password_hereThis tool only reads. It connects in read-only mode and issues nothing but
SELECTqueries. For extra safety, point it at a read-only role. Your.envis git-ignored and never leaves your machine.
python extract.py --schema public --out schema.jsonOptions:
| Flag | What it does |
|---|---|
--schema NAME |
Which schema to read (default: public) |
--out FILE |
Output JSON file (default: schema.json) |
--exact-rows |
Use exact COUNT(*) per table instead of the fast planner estimate. Accurate, but slow on large databases. |
--validate |
Confirm each inferred relationship by actually joining the two columns and measuring the match rate (see below). Read-only, but slower; large tables are guarded by a per-query timeout. |
python build_viewer.py --data schema.json --out viewer.html --title "My database"Open viewer.html in any browser. That's it.
Most databases follow a naming convention: a foreign key column is named after
the table it points to, e.g. orders.user_id → users.user_id. db-schema-3d
uses exactly this signal:
A column whose name matches another table's single-column primary key name is treated as a link to that table.
This works whether or not real FOREIGN KEY constraints are declared — which is
useful, because plenty of real databases have implied relationships that were
never formally constrained.
Because it's an inference, links start out grey ("inferred, unverified"). If
you run extract.py --validate, the tool joins each pair of columns and measures
how many child values actually exist in the parent:
| Match rate | Classification | Meaning |
|---|---|---|
| ≥ 85% | strong (teal) | Almost every value lines up — a real relationship |
| 15–85% | weak (blue) | A partial match — often a soft or historical link |
| < 15% | faint (brown) | Columns share a name but the data barely overlaps — likely coincidental |
| (timed out / type mismatch) | unverified (grey) | Couldn't be measured safely |
The synthetic demo is pre-validated so you can see all four kinds at once.
Both the extractor and the demo generator produce the same simple shape, so you can also generate it from any source you like (another database, a hand-written file, etc.):
See examples/make_ecommerce.py for a fully worked
example you can copy.
- PostgreSQL only for extraction. (The viewer itself renders any valid
schema.json, so other databases can be supported by writing an extractor.) - Relationships are inferred from naming, not read from declared foreign
keys — run
--validatewhen you need confidence. - Composite (multi-column) primary keys are not used as link targets.
- Very large schemas (many hundreds of tables) render, but the force layout gets busy; use search and the "hide unconnected tables" toggle.
db-schema-3d/
├─ extract.py # PostgreSQL -> schema.json (read-only)
├─ build_viewer.py # schema.json + library -> single HTML file
├─ viewer/
│ ├─ template.html # the 3D viewer (English UI)
│ └─ 3d-force-graph.min.js # bundled renderer (MIT, by @vasturiano)
├─ examples/
│ ├─ make_ecommerce.py # generates the synthetic demo schema
│ └─ ecommerce_schema.json # the demo data
├─ dist/
│ └─ demo.html # pre-built demo — open this first
└─ docs/
MIT. Bundles 3d-force-graph by Vasco Asturiano, also MIT.
{ "meta": { "schema": "public", "table_count": 17, "validated": true }, "nodes": [ { "id": "orders", "rows": 210000, "deg": 6, "s": "data" } ], "links": [ { "source": "orders", "target": "users", "col": "user_id", "pcol": "user_id", "strength": "strong", "rate": 1.0 } ], "cols": { "orders": [ { "n": "order_id", "pk": true }, { "n": "user_id", "pk": false } ] } }