dnd5e-character-builder
Build, level up and check D&D 2024 (SRD 5.2.1) character sheets in Python.
A sheet written by a person or a model is free text: "Rogue (Arcane Trickster)", "Shield — level 1, prepared". This library reads it against the rules, lists every problem at once, writes in the numbers the rules derive, and says what the character may still pick now or at the next level. It sits on nat20's rules engine and SRD corpus; nat20 decides whether a build is constructible and derives its HP, AC, saves and slots. This library adds the parts of a sheet nat20 takes as given: class spell lists, prepared and cantrip counts, wizard spellbooks, feat slots, choice counts, skills and ability-score generation.
pip install dnd5e-character-builder
Python 3.14 or later.
Use
from dnd5e_character_builder import Feat, Sheet, check, fill, level_up, options
sheet = Sheet.model_validate({
'name': 'Ana',
'species': 'Human',
'class': 'Fighter',
'level': 4,
'subclass': 'Champion',
'background': 'Soldier',
'abilities': {'str': 17, 'dex': 15, 'con': 14, 'int': 8, 'wis': 12, 'cha': 10},
'skills': ['Athletics', 'Intimidation', 'Perception', 'Survival', 'Acrobatics'],
'feats': ['Savage Attacker', 'Alert', Feat(name='Grappler', level=4)],
'choices': ['Defense'],
'inventory': ['Chain Mail', 'Longsword', 'Shield'],
})
sheet = fill(sheet) # max_hp, ac, saving_throws and spell_slots from nat20
for issue in check(sheet): # every problem at once
print(issue.severity, issue.code, issue.message)
options(sheet) # every pick at this level (subclass, feature choices, feat slots, skills, spells) and what fills it
level_up(sheet, 5) # what level 5 gives automatically and which picks it makes due
level_up and options report; neither changes the sheet. Write the picks, then run fill and check.
build_spec(sheet) and derive(sheet) give nat20's CharacterBuildSpec and DerivedSheet for a sheet, for hosts
that run nat20 combat.
The sheet
| Field | Holds |
|---|---|
species, class, subclass, background |
Names as written. A subclass in parentheses after the class is read too. |
| A background the content does not know is a custom one: +2/+1 or +1/+1/+1 to any abilities, any two skills and any one origin feat. | |
level |
1-20, one class. |
abilities |
Final scores by str ... cha. |
skills, expertise |
Skill names; "Stealth (expertise)" in skills also counts as Expertise. |
feats |
Feat(name, level). level is the class level whose Ability Score Improvement the feat took; leave it None for an origin feat. A bare string is a feat with no level. |
choices |
Class, subclass and species options by name: "Archery", "Agonizing Blast", "Protector". |
spells |
Cantrips known and spells prepared. |
spellbook |
A wizard's level 1+ spells. |
magic_initiate |
One record per Magic Initiate: list, ability, two cantrips, one level 1 spell. |
max_hp, ac, spell_slots, saving_throws |
The sheet's numbers; fill replaces them. |
Issues
Each Issue has a stable code, a severity (error breaks the rules, warning is likely wrong, info is a
note), the sheet field, a message and a detail dict.
| Code | Severity | Meaning |
|---|---|---|
build.rejected |
error | nat20 cannot construct the build; its reason is in detail.reason |
class.unknown, class.multiclass |
error | No class the content knows, or more than one (multiclass is not supported yet) |
species.unknown, species.missing |
error | |
subclass.unknown, subclass.missing |
error | Not in the content, or none chosen at the subclass level |
feat.unknown |
error | Not in the content |
feat.no_slot |
error | Nothing gives this feat: no Ability Score Improvement at that level, the slot is taken, or an origin feat beyond the background's and species' |
feat.level_missing |
info | A feat in an Ability Score Improvement without the level it was taken at; it is checked against the first free one |
feat.slot_empty |
warning | An Ability Score Improvement level with no feat recorded |
feat.missing |
warning | The background's feat (a custom background's chosen origin feat) is not on the sheet |
abilities.not_buildable |
warning | Not the standard array or a 27-point buy plus the background's increase and the Ability Score Improvements taken |
abilities.over_cap |
error | Above 20 (30 with an Epic Boon) |
saves.mismatch |
error / warning | Not the class's saving throws (warning when the sheet lists none) |
skills.unknown |
error | Not a skill |
skills.too_many, skills.too_few |
warning | More or fewer than background, class, species, subclass and feats give |
skills.granted_missing |
warning | A skill given outright is not on the sheet |
expertise.too_many, expertise.not_proficient |
error | |
choice.unknown, choice.too_many |
error | Not an option this build has, or more picks than its level allows |
choice.missing |
warning | Picks still due |
spell.unknown |
error | Not in the content |
spell.not_on_list |
error | Not on the class list and nothing else grants it |
spell.too_many_cantrips, spell.too_many_prepared |
error | Over the class table; granted spells do not count |
spell.above_slot |
error | Prepared above the highest spell slot |
spellbook.missing, spellbook.not_wizard_spell |
error | Prepared but not in the book; a book entry that is not a level 1+ wizard spell |
spellbook.too_few |
warning | Fewer than 6 + 2 per level after the first |
magic_initiate.not_on_list, magic_initiate.too_many |
error | |
magic_initiate.unrecorded |
info | A Magic Initiate whose picks are not recorded |
max_hp.mismatch, ac.mismatch, spell_slots.mismatch |
warning | The sheet's number differs from the derived one |
numbers.unchecked |
info | HP, AC and slots not compared because something the derivation needs is unknown |
Spells a character has without preparing them do not count against the class table: species and subclass grants, spells a feature's own text grants ("You always have the Hunter's Mark spell prepared"), the Circle of the Land table the sheet matches best, Mystic Arcanum, Magic Initiate, and the College of Lore's Magical Discoveries.
Homebrew
load_content(*paths) layers YAML or JSON files (or folders of them) over the SRD; pass the result as
content= to any call. A file holds nat20-schema entries and this library's tables:
subclasses: [...] # nat20 schema; also classes, species, backgrounds, feats, features, spells
spell_lists: # class -> spell level -> spells added to its list; a name with no entry is known by name and level
wizard: {1: [Chromatic Orb], 2: [Dragon's Breath]}
subclass_spells: # always-prepared spells by class level
oath-of-glory: {3: [Guiding Bolt, Heroism], 5: [Enhance Ability, Magic Weapon]}
subclass_spell_choices: # one table per option, as Circle of the Land
circle-of-the-reef: {shallows: {3: [Fog Cloud, Water Breathing]}}
class_tables: # cantrips and prepared per level, 20 each; a casting subclass also names progression and spell_list
eldritch-knight: {cantrips: [...], prepared: [...], progression: third, spell_list: wizard}
aliases:
subclasses: {glory: oath-of-glory}
A path may also be a mapping of the same shape. load_content(loader=...) builds on your own nat20 AssetLoader instead of nat20's bundled corpus.
Loading refuses anything the library could not check, with a ContentError naming it: a spellcasting class without a
table or spell list, a subclass with a Spellcasting feature and no table, a subclass whose spells feature grants no
spells, a spell listed at the wrong level, an alias to nothing. Entries without nat20 provenance get a placeholder one
so they pass nat20's schema.
Limits
Single class only; see BACKLOG.md for the rest.
Data and attribution
- Rules engine and SRD corpus: nat20 (
dnd5e-engine,dnd5e-srd-data), MIT. - Class spell lists and the Cantrips and Prepared Spells columns:
5e-SRD-Spells.jsonand5e-SRD-Levels.jsonfrom 5e-database (src/2024/en, commit6f6299e), MIT, vendored unchanged insrc/dnd5e_character_builder/data/5e-databasewith its licence. Two columns are corrected from the SRD indata/srd.yaml. - This work includes material from the System Reference Document 5.2.1 ("SRD 5.2.1") by Wizards of the Coast LLC, available at https://www.dndbeyond.com/srd. The SRD 5.2.1 is licensed under the Creative Commons Attribution 4.0 International License, available at https://creativecommons.org/licenses/by/4.0/legalcode.
This library's code is MIT; see LICENSE.
Develop
uv sync
uv run ruff format --check src tests && uv run ruff check src tests && uv run mypy src tests && uv run pytest
Release
Bump version in pyproject.toml, commit, then git tag v<version> && git push origin v<version>. The
release.yml workflow runs the checks and publishes to PyPI through Trusted Publishing.
Metadata
Release files for dnd5e-character-builder 0.1.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | Actions |
|---|---|---|---|---|
| dnd5e_character_builder-0.1.0.tar.gz | 135.7 kB | With provenance | Details for dnd5e_character_builder-0.1.0.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| dnd5e_character_builder-0.1.0-py3-none-any.whl | Python 3 | none | any | Details for dnd5e_character_builder-0.1.0-py3-none-any.whl |
Total release size: 276.9 kB
Release files / dnd5e_character_builder-0.1.0.tar.gz
| Download URL | dnd5e_character_builder-0.1.0.tar.gz |
|---|---|
| Size | 135.7 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
dfacd124c039f4b1a135c12bed7ded20097b50a864d6ef3888a9c5274da5e75b
|
|
BLAKE2b-256 checksum How to use checksums |
36bf1a8e2a1e46de0d19cceead19edb86df9a425fb9e8051e7b63abad10cee3c
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.16
|
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 Nov 2, 2026.
Transparency logRelease files / dnd5e_character_builder-0.1.0-py3-none-any.whl
| Download URL | dnd5e_character_builder-0.1.0-py3-none-any.whl |
|---|---|
| Size | 141.3 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
23758558b6667b2eed0d37dfcf27fb997f5cbe686d7e52b7230e1b8481d0b1c0
|
|
BLAKE2b-256 checksum How to use checksums |
45d44c736e189aec3dc7127fcd4a71fafc4e1e9f4915fd73fa923df523b7bfbd
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.16
|
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 Nov 2, 2026.
Transparency logSecurity for dnd5e-character-builder 0.1.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.
Attestation for dnd5e_character_builder-0.1.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 Nov 2, 2026.
Transparency logA 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.