Skip to content

Repository files navigation

5e-database-sqlite

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]).

Using the database

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.

What's inside data

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.

Installing as a library

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.

Python API

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.

Discovering a collection's fields

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 lists desc, range, level, damage, school, classes, … — Collection[M] is generic, so srd.spells types as Collection[Spell] and srd.races as Collection[Race] without running anything.
  • At runtime, Spell.model_fields (or Race.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.py are the canonical definitions — see "What's inside data" 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).

License

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.

Setup (building it yourself)

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 install

Build the database

The 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.sqlite3

build 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.

Releasing

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:

  1. Runs the stable gate (tests + validate --edition 2014).
  2. Builds the 2014/en database and attaches it to a new GitHub Release.
  3. Builds the sdist/wheel with the package version set to the tag (PEP 440-normalized: v5.10.05.10.0, v5.10.0-r25.10.0.post2) and publishes to PyPI (bundling both editions' databases — see step 4).
  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)

Vendoring this repo in your own build

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.

Test

poetry run pytest

About

D&D 5e SRD data as a single SQLite file, built from the JSON published by 5e-bits/5e-database.

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages