Getting started
Install the game, make a mod directory, enable it, restart. This page is the durable reference for that loop; the tutorial ladder builds on it.
Run the game first
Open the packaged desktop app and, on first boot, choose your legally
obtained Pokemon Red .gb file (only the canonical 1 MiB US ROM is
accepted; the importer verifies SHA-1
ea9bcae617fdf159b045185467ae58b2e4a48b9a). Import takes a few seconds and
the game starts. From a source checkout, love . after setup — see
Developer Setup.
Where mods live
The loader scans one virtual mods/ root. LÖVE fuses two real locations
into it (src/mods/Loader.lua:_discover):
mods/inside the game folder (a source checkout or an unpacked app)mods/inside the LÖVE save directory — the same placesave.lualives:- macOS:
~/Library/Application Support/LOVE/pokemon-love2d/mods/(without theLOVE/segment in a packaged build) - Linux:
~/.local/share/love/pokemon-love2d/mods/ - Windows:
%APPDATA%\love\pokemon-love2d\mods\
- macOS:
Either location works; the save directory is the one that survives app updates.
The minimal mod
mods/my_first_mod/
├── manifest.json
└── main.lua
manifest.json:
{
"id": "my_first_mod",
"name": "My First Mod",
"version": "1.0.0",
"entry": "main.lua",
"api": 2
}
api: 2 opts into strict schema validation — typos become load errors with
suggestions instead of silent misses. Every other field is optional; the
full list is in the manifest reference.
main.lua returns an entry function that receives the mod object:
return function(mod)
mod.log:info("hello from a native mod")
-- patch deep-merges onto the imported record; only speed changes
mod.content.pokemon:patch("GROWLITHE", {
baseStats = { speed = 90 },
})
-- react to the one gift-mon seam every story gift passes through
mod.events:on("pokemon.before_give", function(gift)
mod.log:info("giving %s at level %d", gift.species, gift.level)
end)
end
Record fields are camelCase (baseStats, catchRate, spriteFront) —
they come from the importer, not from JSON. Writing base_stats is the
classic mistake; under api = 2 the loader rejects it with
did you mean "baseStats"?. See Data Model.
Scaffold instead of hand-writing
The modkit CLI seeds the same layout with a valid manifest and a commented entry file:
python3 tools/modkit.py scaffold my_first_mod
python3 tools/modkit.py validate my_first_mod
validate drives the real engine loader headlessly, so a mod that passes
will not surface load errors in-game. See Modkit CLI.
Enabling, disabling, and restarting
Mods load enabled by default. Enablement persists in the normal
options.lua beside audio and display settings — starting a new game never
resets it. options.mods[id] = false means disabled; missing means
enabled. Toggle in-game from the mod manager (Options menu), or edit the
file. Content changes take effect on the next launch: content registries
freeze after the boot merge (Lifecycle).
While developing, run with POKEPORT_DEV=1: F5 hot-reloads mod content
without restarting, and the loader warns when a mod requires private engine
modules without declaring the matching permission.
When something breaks
A broken mod never takes the game down. Load failures — bad manifest, schema violations, missing dependencies, an entry chunk that throws — are reported in the mod manager against the failing mod, and every registration that mod made is rolled back. Fix the reported field and restart.
The rule that has no exceptions
Do not put ROM-derived content in a mod. Ship original assets, or an asset
transform that regenerates derived art from the player's own imported cache
(Art Pipeline). The loader never imports or executes
ROM-hack patches; the supported content source remains the verified base
ROM plus native mods. modkit pack refuses to package cache-derived bytes.
Where to next
| Goal | Page |
|---|---|
| See a change on screen in ten minutes | Tutorial 01 |
| Understand what loads when | Lifecycle |
| Learn the five registry verbs | Registries |
| Look up one task | Cookbook |
| Look up a record's fields | Reference: Registries |
Start here
Concepts
Tutorials
- The ladder
- 01 Sprite and Text Tweak
- 02 Balance Patch
- 03 New Species
- 04 New Item and Ball
- 05 New Move
- 06 NPC and Dialogue
- 07 New Map
- 08 Quest
- 09 Custom Music
- 10 New Mechanic
- 11 Custom UI Screen
- 12 Mini Total Conversion
Reference
Guides
- Art Pipeline
- Audio Authoring
- Preparing Your Mod for Gen 2
- Translations
- Total Conversions
- Link Compatibility
- Publishing a Mod
- Docs Style Guide
Playing
Engine 1.0.0 · mod API 2 · repository · Discord
This project ships no ROM data. All game content is decoded on the player's machine from their own verified Pokemon Red ROM; mods ship recipes and original assets, never extracted content.