Skip to main content

django-joist

Latest version on PyPI Downloads Tests Python versions Django 5.2+ License: MIT AND OFL-1.1

A live database structure viewer for Django. Joist introspects your live schema and renders it as a scrollable, zoomable ER diagram right inside your app, so you can see how tables actually connect without opening a DB client. It reads structure only by default — tables, columns, keys, indexes, foreign keys; row data is never queried or exposed in this mode.

django-joist is a Django port of the concept implemented by Laravel Truss (MIT, Alberto Arena), which inspired it. The name, branding and Django wiring are original; selected front-end code is adapted under MIT with attribution.

Features

  • Live ER diagram of your database, rendered with Mermaid — self-hosted, no CDN, no build step.
  • Focus mode: a table and its foreign-key neighbours, with a searchable picker that stays usable on schemas with hundreds of tables.
  • Filter by table name; toggle native DB types against Django-style labels.
  • Map-style pan and zoom, auto-fit with a legible floor, plus a Fit button.
  • Exports: PNG/SVG from the browser; DBML, JSON, CSV, Markdown data dictionary, Mermaid, or a token-trimmed llm format — from the dashboard or from CI with manage.py joist_export. Deterministic: the same schema always produces the same bytes, so joist_export --check fails the build when a committed schema file drifts.
  • Feed your real structure to a coding agent as grounding context: annotations for business meaning, --compact to trim tokens, --focus to narrow to one neighbourhood. Only the schema is read — row data is never touched.
  • Schema diff: what changed since the last migration, in the dashboard's Changes panel and via manage.py joist_diff.
  • Schema doctor: manage.py joist_doctor reviews the structure for problems (missing primary keys, unindexed foreign keys, duplicate indexes, risky types) — deterministic, structure-only, safe in CI, with --fail-on exit codes; the same findings power the dashboard's Health panel.
  • Multiple databases: list aliases under JOIST["connections"] and switch diagrams from the toolbar, each scoped to its own database.
  • Light/dark drafting-grid theme, or bring your own: a handful of semantic colour/font knobs re-skin the whole dashboard, served as a same-origin stylesheet (CSP-safe, no inline styles).
  • Rebuilds itself: the cached snapshot refreshes after every migrate and degrades gracefully when the cache store is unreachable.
  • Production-safe by design: routes answer 404 unless enabled, denials never confirm the dashboard exists, and excluded tables are filtered server-side so their structure never reaches the browser.

Installation

uv add django-joist

Add the app and mount the dashboard:

# settings.py
INSTALLED_APPS = [
    ...,
    "django_joist",
]

# urls.py
from django.urls import include, path

urlpatterns = [
    ...,
    path("joist/", include("django_joist.urls")),
]

Requires Python 3.10+ and Django 5.2+. No migrations, no models, no collectstatic needed (assets are served by the package, gated with the rest of the dashboard). The dashboard HTML is a template shipped inside the app, so your TEMPLATES must keep "APP_DIRS": True for django_joist (the default in django-admin startproject); if you disabled it, add the package's template directory to DIRS.

Development-only install

To keep Joist out of production builds entirely, declare it as a development dependency of your project, the way composer require --dev would:

uv add --dev django-joist              # deploy with: uv sync --no-dev
poetry add --group dev django-joist    # deploy with: poetry install --without dev

Unlike Laravel's auto-discovery, Django imports everything named in INSTALLED_APPS and urls.py, so a production build without the package fails at startup unless both entries are conditional. Test for the package itself rather than for DEBUG, so a deploy that turns DEBUG on by mistake still boots:

# settings.py
from importlib.util import find_spec

if find_spec("django_joist"):
    INSTALLED_APPS += ["django_joist"]

# urls.py
from importlib.util import find_spec

if find_spec("django_joist"):
    urlpatterns += [path("joist/", include("django_joist.urls"))]

Removing Joist leaves nothing behind: it has no models and no migrations. The management commands go with it, so a CI job that runs joist_doctor or joist_export --check must install the development dependencies.

To run Joist gated on staging or production instead, install it as a regular dependency (above) and see the next section.

Quick start

By default Joist is enabled when DEBUG = True. Start your dev server and visit /joist/.

To run Joist gated on staging or production, set both switches:

JOIST = {
    "enabled": True,
    "authorization": {
        "allowed_emails": ["ada@example.com", "grace@example.com"],
    },
}

Outside DEBUG the shipped default authorizer admits only allow-listed emails and fails closed on an empty list. To authorize by role instead, point authorization.callable at your own request -> bool (a callable or a dotted path); it fully replaces the default. Denials always return 404.

Your project's AuthenticationMiddleware must run for the mounted URLs (the normal case). Joist's own guard is part of the views and cannot be configured away.

Command line

python manage.py joist_show                 # the structure as a terminal table
python manage.py joist_open                 # print/open the dashboard URL
python manage.py joist_rebuild              # rebuild the cached snapshot (CI)
python manage.py joist_diff                 # what changed since the last migration
python manage.py joist_doctor               # structure review; exits non-zero on findings
python manage.py joist_export               # DBML to stdout by default
python manage.py joist_export --format=json --output=docs/schema.json
python manage.py joist_export --format=dbml --output=docs/schema.dbml --check  # CI drift check
python manage.py joist_export --tables=orders,order_lines --focus=orders --depth=1 --compact
python manage.py joist_mcp                  # serve the MCP server over stdio (for coding agents)
python manage.py joist_install_agent        # install agent skills and MCP project configuration

joist_doctor --format=json and --fail-on=warning make it a CI gate; joist_export --check regenerates, compares, writes nothing and exits 1 on drift (2 on usage errors), so a migration that changes the schema without refreshing the committed file fails the build.

As AI context

The same export doubles as grounding context for a coding agent, so it stops inventing columns:

python manage.py joist_export --format=llm          # dense, token-trimmed plaintext
python manage.py joist_export --compact             # drop defaults and non-unique indexes
python manage.py joist_export --focus=orders --depth=1

Annotations add the business meaning a type cannot — declare them in settings under JOIST["annotations"] (per-table, per-column, global notes) or let Joist read native database comments (Postgres/MySQL) by keeping "database" in annotations["source"]. The llm format and Markdown/DBML all render annotations; --no-annotations strips them.

Programmatically, the builder is immutable and mirrors the CLI byte-for-byte:

from django_joist import snapshot

text = (
    snapshot()
    .only(["orders", "order_lines"])
    .focus("orders", depth=1)
    .compact()
    .to_dbml()
)

MCP server

Joist can also serve the same structure over the Model Context Protocol, so an agent queries the live schema on demand instead of being handed a paste. Point your MCP client at the management command as a stdio server:

python manage.py joist_install_agent

Run this from the Django project root. It configures Claude Code, Codex and Cursor for this project: .mcp.json, .codex/config.toml and .cursor/mcp.json launch joist_mcp with the current Python interpreter and an absolute path to manage.py. It installs the bundled joist-schema and joist-model-review skills under .claude/skills and .agents/skills, and adds a short pointer to AGENTS.md. Cursor also reads .agents/skills. Existing unrelated configuration is retained; if a Joist entry or skill has been customized, the command stops and asks you to reconcile it. Re-running after an install makes no changes. Use --agent claude, --agent codex or --agent cursor to select clients (repeat the flag for more than one). For a different interpreter, pass --python /path/to/python.

For model changes, Django's makemigrations creates migrations from model and migration-file state. Joist can inspect the live schema and, after migrate, joist_diff can show the resulting structural change. Joist captures the live structure before migrate and records a baseline only when the structure actually changes; an empty cache, a no-op migrate, or flush does not erase the previous diff.

For a manual setup, point your MCP client at the management command as a stdio server:

{
  "mcpServers": {
    "joist": {"command": "python", "args": ["manage.py", "joist_mcp"]}
  }
}

The server must launch from the Django project root with the interpreter that can import your settings (for containerised apps, wrap it: docker exec -i <app> python manage.py joist_mcp). Register it with your agent:

Agent Command
Claude Code claude mcp add --scope project joist -- python manage.py joist_mcp
Codex (OpenAI) codex mcp add joist -- python manage.py joist_mcp
Cursor same JSON in the project's .cursor/mcp.json

It exposes five schema tools - list_tables, describe_table, get_schema, focus_table and get_structural_review - plus the joist://schema resource. Every one rides the same cached, exclusion-filtered snapshot as the dashboard, so their format, compact, focus, connection and annotation behaviour is identical to the CLI. It is structure only and adds no dependency: the newline-delimited JSON-RPC stdio transport lives in the package itself.

Configuration

Everything lives in one JOIST dict; unset keys keep their defaults (nested dicts merge key-by-key). The common knobs:

Key Purpose
enabled Master switch; defaults to DEBUG. Off = every route 404s
connections Visualizable database aliases + per-alias overrides, e.g. {"analytics": {"excluded_tables": [...]}}; empty = just default; an omitted alias selects the first managed alias
fallback.enabled Opt in to replaying project migrations on in-memory SQLite when a database is unreachable; off by default, so connection errors surface normally
excluded_tables Hidden server-side (never sent to the browser). Django bookkeeping tables are pre-listed
authorization.callable / .allowed_emails Who may view outside DEBUG; a custom callable replaces the email allow-list
cache.ttl / cache.alias Snapshot cache (seconds, <= 0 = forever) and which CACHES entry to use
diagram.type_labels native (default) or django short labels
diagram.mermaid_url Point at your own copy/CDN; default self-hosts from the gated asset route
focus.default_depth FK-neighbour hops shown on focus
large_schema.warn_above Table count that triggers the "use focus/filter" hint
diff.enabled / diff.dir Schema-diff switch and where the structure-only baseline lives (default BASE_DIR/var/joist)
doctor.* Preset, per-rule severity/enable, ignore patterns, fail_on, dashboard panel switches
annotations.* Global notes, per-table/per-column meaning, comment sources
theme.* Semantic colour/font knobs for both light and dark
base_url Used by joist_open to build a browsable URL

During normal schema reads, the baseline file under diff.dir is Joist's only disk output; it is derived and safe to delete — worth gitignoring alongside var/. The optional joist_install_agent command writes agent configuration to the project.

The no-data promise

Structure is the CREATE TABLE definition: tables, columns (name, native type, nullability, defaults), primary keys, indexes, foreign keys with referential actions, and native comments. With the default settings, row contents are never queried by the dashboard, the API, the MCP server, the CLI, or the doctor. Enabling fallback.enabled executes project migrations when a connection fails; RunPython and RunSQL migrations can perform arbitrary queries and side effects, including against another database alias. Config excluded_tables are stripped server-side, so an excluded table never reaches the client, the diff, or an export.

Development

uv sync --extra dev   # creates .venv with the package editable and the test tools
uv run pytest

Static checks are part of the same extra and run in CI - mypy on the shipped package against django-stubs, and ruff for lint (it is not used as a formatter). Install the hook to run both before every commit:

uv run mypy
uv run ruff check .
uv run pre-commit install

Supported versions are declared once, in pyproject.toml, and the CI matrix is derived from them rather than the other way round: requires-python gives the Python floor (3.10), the Framework :: Django :: * classifiers give the Django list, and every combination of the two that Django itself supports runs — all five Pythons on 5.2, only 3.12+ on 6.0 and 6.1. Because Django>=5.2 carries no ceiling, the newest Django release is additionally tested in its own job, so a new Django is caught here instead of by users — a version added to the metadata belongs in .github/workflows/tests.yml (and in release.yml, which repeats the matrix to gate a tag) in the same commit.

The suite runs on SQLite by default. The PostgreSQL and MySQL lanes are the same suite against a real server: install the driver (psycopg[binary] for PostgreSQL, pymysql[rsa] for MySQL — tests/settings.py registers it as MySQLdb) and point the run at one:

JOIST_TEST_ENGINE=postgres JOIST_TEST_USER=joist JOIST_TEST_PASSWORD=password uv run pytest

JOIST_TEST_HOST, JOIST_TEST_PORT, JOIST_TEST_DB and JOIST_TEST_DB_SECONDARY override the rest. tests/test_backends.py skips the server-only assertions when the lane is SQLite, and its canary fails the run if the snapshot fell back to SQLite instead of reading the server.

The client-side logic has its own suite on Node's built-in test runner (no build step: the assets ship as native ES modules), and the browser lane drives the real app in Chromium - plus Firefox for the label-geometry spec, which is the one thing only a second engine can check (see its comment for why).

npm install
npm test                                  # the client-side logic and the stylesheet invariants
npx playwright install chromium firefox   # once
JOIST_PYTHON=.venv/bin/python npm run test:e2e

npm run test:e2e starts the test project itself (tests/browser/serve.py) on a throwaway SQLite file, so it needs the interpreter that has Django and this package installed: JOIST_PYTHON points at it, defaulting to python.

License

MIT. See LICENSE (includes the attribution to Laravel Truss). Vendored assets carry their own licenses (Mermaid MIT, IBM Plex Mono SIL OFL) under src/django_joist/static/joist/.

Metadata

Release files for django-joist 0.3.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for django-joist 0.3.0
File Size Uploaded Provenance Actions
django_joist-0.3.0.tar.gz 1.2 MB With provenance Details for django_joist-0.3.0.tar.gz

Built distribution (wheel)

Table of built distributions (wheels) for django-joist 0.3.0
File Interpreter ABI Platform Actions
django_joist-0.3.0-py3-none-any.whl Python 3 none any Details for django_joist-0.3.0-py3-none-any.whl

Total release size: 2.4 MB

Release files / django_joist-0.3.0.tar.gz

Download URL django_joist-0.3.0.tar.gz
Size 1.2 MB
Tags Source
SHA-256 checksum
How to use checksums
e8d476e5ba72e5fe99b6110b09d1fb28745b919e59d8511b14dc11290025db20
BLAKE2b-256 checksum
How to use checksums
7fac937fcbdda5ae768b51e1f5cf68489e9bca98ad1deb2f42231d22333ed24c
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 30, 2026.

Transparency log

Release files / django_joist-0.3.0-py3-none-any.whl

Download URL django_joist-0.3.0-py3-none-any.whl
Size 1.2 MB
Tags Python 3
SHA-256 checksum
How to use checksums
94395a83c5d64d5321d8c44c7163f7c9aec7b67a915c14b699f0bf9aeb0d1139
BLAKE2b-256 checksum
How to use checksums
fd4e988175423a80be7f72183edf6ee4515b65746c5baba788cf4e040bca1133
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 30, 2026.

Transparency log

Security for django-joist 0.3.0

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

Full provenance

All 2 files in this release have been uploaded with provenance metadata.

Select from 2 files

Attestation for django_joist-0.3.0.tar.gz

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 30, 2026.

Transparency log

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page