D&D 5e SRD data as a single SQLite file, built from the JSON published by 5e-bits/5e-database.
Currently tracking upstream release
v5.10.0.
The pin lives in pyproject.toml ([tool.dnd5e-database-sqlite]).
Grab dnd5e-<edition>-<language>-<tag>.sqlite3 from the
Releases page and
open it with any SQLite client — no server, no dependencies. (Releases are
published automatically when a vX.Y.Z tag matching the upstream release is
pushed; if none exists yet for the version you need, see
Build the database below to make your own in one
command.) Every collection is a table (monsters, spells, equipment, …)
with three columns:
| column | meaning |
|---|---|
index |
unique id, primary key ("adult-black-dragon") |
name |
display name, indexed |
data |
the complete record as JSON — same shape as the dnd5eapi.co API |
-- simple lookup (uses the name index)
SELECT data FROM spells WHERE name = 'Fireball';
-- query into the JSON
SELECT name, json_extract(data, '$.challenge_rating') AS cr
FROM monsters WHERE cr >= 20 ORDER BY cr DESC;
-- expand a JSON array: every monster immune to poison
SELECT m.name
FROM monsters m, json_each(m.data, '$.damage_immunities') d
WHERE d.value = 'poison';import sqlite3, json
db = sqlite3.connect("dnd5e-2014-en-v5.10.0.sqlite3")
fireball = json.loads(db.execute(
"SELECT data FROM spells WHERE name = ?", ("Fireball",)
).fetchone()[0])
print(fireball["desc"][0])JSON functions require SQLite 3.38+ (or any build with the JSON1 extension —
that's virtually every runtime since 2016). The metadata table records which
upstream release, edition, and language the file was built from, plus license
and attribution.
data is the complete record, not a subset — every field the collection's
model defines, nested objects and arrays intact, dropped keys only where the
original was absent (nulls aren't stored). It's a value-exact round trip
of the upstream JSON record: parsing data gives back exactly the keys and
values upstream published (key order and whitespace follow our serializer,
not upstream's files), cross-references included (school, classes,
damage.damage_type, etc. are all {index, name, url} pointers you resolve
with a second query — there are no SQL foreign keys).
The shape is per-collection, not uniform: a spells row's data follows the
spell schema, a races row's follows the race schema. There's no single
"record" shape across tables. The canonical definition of each collection's
shape lives in this repo's pydantic models (one file per collection —
dnd5e_db/models/<edition>/<collection>.py), which mirror upstream's zod
schemas 1:1; each file's docstring names the exact upstream .ts schema it
mirrors, under 5e-bits/5e-database's src/*/schemas/
— useful as a language-neutral reference if you're not working in Python.
pip install 5e-database-sqlite(Installing straight from git works too:
pip install git+https://github.com/vkama/5e-database-sqlite.git.)
SRD.open() and SRD2024.open() both work right away with no local checkout
needed — 2014/en and 2024/en databases both ship bundled inside the package.
From Python you don't need SQL at all — every record is a typed pydantic model with autocomplete:
from dnd5e_db.srd import SRD
with SRD.open() as srd: # 2014/en: builds from data/ in a checkout,
# or opens the db bundled in the package otherwise
aboleth = srd.monsters["aboleth"] # by index
aboleth.challenge_rating # 10.0
aboleth.speed.swim # "40 ft."
srd.spells.named("Fireball") # by display name
[m.name for m in srd.monsters if m.challenge_rating >= 20]
[s.name for s in srd.spells if s.level == 3 and s.school.index == "evocation"]Collections support [], .get(), in, len(), iteration, and .named() —
plain Python, no query language.
srd.spells and srd.races don't hand you dicts — each item is a specific
model instance (Spell, Race), so what properties it has is discoverable
the same way as any typed Python object:
- Autocomplete.
srd.spells["fireball"].in an editor/type checker listsdesc,range,level,damage,school,classes, … —Collection[M]is generic, sosrd.spellstypes asCollection[Spell]andsrd.racesasCollection[Race]without running anything. - At runtime,
Spell.model_fields(orRace.model_fields) gives you{field_name: FieldInfo(type, ...)}for every field, nested model types included;Spell.model_json_schema()expands that recursively into a full JSON Schema document. - In source,
dnd5e_db/models/srd2014/spells.py/.../races.pyare the canonical definitions — see "What's insidedata" above.
The 2024 SRD preview reads the same way through SRD2024.open() — identical
interface, 2024 collections (species/subspecies instead of
races/subraces, plus poisons and weapon_mastery_properties; no spells
or rules — upstream hasn't ported them yet, and monsters currently holds only
a handful of records).
The D&D SRD content is © Wizards of the Coast LLC, released under
CC-BY-4.0; each database file
embeds the required attribution in its metadata table. The upstream JSON is
maintained by 5e-bits/5e-database
(MIT). The build tooling in this repo is released under MIT.
Requires Python 3.14. If it's not already on your system, install it locally with uv (no sudo needed):
uv python install 3.14
poetry env use $(uv python find 3.14)Then:
poetry installThe upstream JSON is already vendored under data/ (checked into git), so a
plain build works right away — no fetch step needed:
poetry run python -m dnd5e_db.build
# -> dist/dnd5e-2014-en-v5.10.0.sqlite3
poetry run python -m dnd5e_db.build --edition 2024 # dist/dnd5e-2024-en-v5.10.0.sqlite3build validates the vendored data against our pydantic models first and
refuses to write if anything doesn't match — a validation failure means
upstream changed shape, not that it's safe to skip.
Bumping to a newer upstream release also involves dnd5e_db.fetch
(re-download the vendored data for a new tag), dnd5e_db.validate (drift
report without building), and dnd5e_db.check (is the pin still upstream's
latest?) — see CLAUDE.md for the full workflow.
Pushing a vX.Y.Z tag matching the pinned upstream release (or a -rN
suffix for a tooling-only re-release of the same data) runs
.github/workflows/release.yml, which:
- Runs the stable gate (tests +
validate --edition 2014). - Builds the 2014/en database and attaches it to a new GitHub Release.
- Builds the sdist/wheel with the package version set to the tag
(PEP 440-normalized:
v5.10.0→5.10.0,v5.10.0-r2→5.10.0.post2) and publishes to PyPI (bundling both editions' databases — see step 4). - A second, independent job runs the 2024 preview's own gate (
pytest -m srd2024+validate --edition 2024), builds the 2024/en database, and attaches it to the same GitHub Release as a second asset. This job only runs after step 1-3 succeed (it needs the release to already exist to upload to), but never blocks them — if the 2024 preview corpus has drifted, only this job fails; the 2014 release and PyPI publish already went out.
workflow_dispatch dry-runs the two database jobs — same gates and builds,
uploaded as workflow artifacts, nothing published. The PyPI job is tag-gated
and simply doesn't run on a manual dispatch, so the sdist/wheel build has no
dry-run mode.
PyPI publishing uses Trusted Publishing
(OIDC) rather than a stored API token — nothing to generate or rotate. It
needed a one-time setup on PyPI before the first release (already done for
this repo; kept here for forks or a project rename): on the
"Publishing" tab of a PyPI
account with rights to the 5e-database-sqlite project name (or via "pending
publisher" if the project doesn't exist on PyPI yet), register a GitHub
publisher with:
| field | value |
|---|---|
| PyPI project name | 5e-database-sqlite |
| Owner | vkama |
| Repository name | 5e-database-sqlite |
| Workflow name | release.yml |
| Environment name | (leave blank) |
If your project wants the built .sqlite3 as part of its build (rather than
just grabbing a released file), pull this repo in as a git submodule and build
from inside it:
git submodule add https://github.com/vkama/5e-database-sqlite.git vendor/5e-database-sqlite
cd vendor/5e-database-sqlite
poetry install
poetry run python -m dnd5e_db.build --out ../../assets # --out sends the db wherever you want it--out is an existing flag on dnd5e_db.build — nothing above needs Poetry
specifically either: the only runtime dependency is pydantic, so pip install pydantic plus python -m dnd5e_db.build --out <path> (run from the submodule
root) works in any Python 3.14 toolchain that doesn't want Poetry involved.
A submodule pins an exact commit, so picking up a newer upstream release means
git submodule update --remote (or checking out a new tag) plus a commit in
your repo — bumps stay explicit and reviewable. Note that vendoring pulls in
the full data/ tree (~7.5 MB) as part of the clone. If you just want the file
without rebuilding anything, see "Using the database" above.
poetry run pytest