Python bindings for bethkit - a fast Rust library for reading and writing Bethesda game plugin and archive files. bethkit.py wraps the bethkit_ffi C ABI via ctypes; no compiler or build tools required.
- Plugin reading - open
.esp/.esm/.eslfiles by path or from in-memory bytes; iterate groups and records; look up records by FormID or EditorID; inspect sub-records - Plugin writing - build new plugins from scratch with
PluginWriter,WritableGroup, andWritableRecord - BSA / BA2 archives - open and extract entries from BSA (TES4/SSE) and BA2 (GNRL/DX10) archives; write new archives with
BsaWriter,Ba2GnrlWriter, andBa2Dx10Writer - String tables - read, edit, and write
.STRINGS/.DLSTRINGS/.ILSTRINGSlocalisation files; apply translation sets withLocalizationSet - Schema - decode sub-records into typed
FieldValuevariants (integers, floats, FormIDs, enums with resolved names, flags, structs, arrays) viaRecordViewandSchemaRegistry - Load-order utilities -
LoadOrder,GlobalFormId, andPluginCachefor winning-override lookups and EditorID search across multiple plugins
| Requirement | Version |
|---|---|
| Python | ≥ 3.10 |
| pydantic | ≥ 2.0 |
The native bethkit_ffi library is bundled in the wheel — no separate installation required.
uv add bethkitor
pip install bethkitfrom pathlib import Path
from bethkit import Plugin, Game
with Plugin.open(Path("Ordinator - Perks of Skyrim.esp"), Game.SKYRIM_SE) as plugin:
print("Masters:", plugin.masters)
print("Kind:", plugin.kind)
for group in plugin:
for child in group:
if hasattr(child, "form_id"):
print(f" 0x{child.form_id:08X} {child.editor_id}")from pathlib import Path
from bethkit import Game, PluginWriter, WritableGroup, WritableRecord
with PluginWriter(Game.SKYRIM_SE) as writer:
with WritableGroup.new(b"NPC_") as group:
rec = WritableRecord.new(b"NPC_", form_id=0x000D62)
rec.add_subrecord(b"EDID", b"MyNPC\x00")
group.add_record(rec)
writer.add_group(group)
writer.write_to_file(Path("MyMod.esp"))from pathlib import Path
from bethkit import Archive
with Archive.open(Path("Skyrim - Meshes.bsa")) as arc:
data = arc.extract("meshes/actors/character/character assets/skeleton.nif")
if data:
Path("skeleton.nif").write_bytes(data)from pathlib import Path
from bethkit import Game, Plugin, PluginCache, PluginKind, LoadOrder
lo = LoadOrder()
lo.push("Skyrim.esm", PluginKind.FULL)
lo.push("MyMod.esp", PluginKind.FULL)
cache = PluginCache()
cache.add("Skyrim.esm", Plugin.open(Path("Skyrim.esm"), Game.SKYRIM_SE))
hit = cache.find_by_editor_id("ArmorIronCuirass")
if hit:
print(hit.global_form_id) # Skyrim.esm:0x012E49
print(hit.record.editor_id)git clone https://github.com/Modding-Forge/bethkit.py
cd bethkit.py
uv sync --extra dev
uv run pytestLinting and type-checking:
uv tool run ruff check src/ tests/
uv tool run pyright src/ tests/- bethkit - the underlying Rust library;
bethkit.pywraps its C ABI
Apache-2.0 - see LICENSE.