# Anatomy of a Factorio mod (2.0 / Space Age) — reference > Research (Grok 4.3 live web, 2026-06-23) cross-checked against Claude's knowledge. For the > deterministic-quality mod project — see `DESIGN.md`. ## 1. Package - Mod = folder or zip. **Distribution zip:** `modname_1.2.3.zip` containing one top folder `modname_1.2.3/`. **Dev:** an unzipped folder (can be named just `modname`) in the mods dir. - Mods dir: Windows `%APPDATA%\Factorio\mods`, Linux `~/.factorio/mods`, macOS `~/Library/Application Support/factorio/mods`. `mod-list.json` tracks enabled mods. - Folder/zip name must agree with `name` + `version` in `info.json`. ## 2. `info.json` (mod root) - **Required:** `name`, `version`, `title`, `author`. **Always set** `factorio_version` ("2.0"). - `dependencies` prefixes: *(none)* = required · `!` incompatible · `?` optional · `(?)` hidden optional · `~` required but does NOT affect load order. Version ops `>= <= = < >`. `base` is auto-depended unless overridden. ## 3. Load stages (strict phases, across all mods at once, dependency-ordered) | Stage | Files (in order) | Purpose | Game access? | |---|---|---|---| | **Settings** | `settings.lua` → `settings-updates.lua` → `settings-final-fixes.lua` | Define mod settings; 3 types: `startup`, `runtime-global`, `runtime-per-user` | No | | **Data (prototypes)** | `data.lua` → `data-updates.lua` → `data-final-fixes.lua` | Build prototypes into global `data.raw` via `data:extend{...}` (items/entities/recipes/tech/…). Build-once, frozen. Can read other mods' prototypes + `settings.startup`. | **No** (no game/state) | | **Control (runtime)** | `control.lua` (+ `require`d files) | Only stage that runs in a loaded save. Event-driven scripting. | **Yes** | **Control essentials:** ```lua script.on_init(fn) -- once, new save / mod added script.on_load(fn) -- every load; MUST NOT write storage script.on_configuration_changed(fn)-- mod/version/prototype change → migrations script.on_event(defines.events.on_tick, fn) ``` - **`storage`** = persistent per-mod saved table. **(Was `global` in 1.1 — renamed in 2.0.)** - Also `remote` (inter-mod), `commands` (custom `/cmds`), `game`, `defines`. - Data stage CANNOT touch runtime; control stage CANNOT define prototypes. ## 4. Other standard files - `locale//*.cfg` — INI with `[category]` sections (`[item-name]`, `[entity-name]`, `[recipe-name]`, `[mod-setting-name]`, …). - `graphics/`, `sounds/` — assets. - `migrations/` — `*.lua` run as control code on loading an older save; `*.json` for prototype renames. - `changelog.txt` — strict format: 99-hyphen separators, `Version:` / `Date:` headers, 2-space category headers (`Bugfixes:`), 4-space `- ` entries. - `thumbnail.png` — mod portal (~144px+). ## 5. Asset paths `"__modname__/graphics/x.png"`. Specials: `__base__` (base game assets/prototypes), `__core__` (engine). ## 6. Settings access from control.lua `settings.startup["k"].value` · `settings.global["k"].value` · `settings.get_player_settings(player)["k"].value`. ## 7. Tooling - `lua-api.factorio.com` — versioned; **prototype docs (data) vs runtime API docs are separate**. - VS Code + **FMTK** (Factorio Modding Tool Kit) + sumneko Lua → autocomplete/diagnostics. - Wiki `Tutorial:Modding`; portal `mods.factorio.com`. Debug: `log()`, `serpent.block()`, F4 menu, `--instrument-mod`, `/c` console. ## 8. 2.0 vs 1.1 must-knows - `global` → **`storage`**. - **Quality / Space Age / Elevated Rails are separate mods** — depend on `quality` / `space-age` explicitly when using their features. - Versioned API docs; `factorio_version: "2.0"`. - Quality fields touch nearly every item/entity prototype; rail/fluid/inserter changes; Space Age adds planets/space platforms. ## Minimal skeleton ``` my-mod/ ├─ info.json ├─ settings.lua (optional) ├─ data.lua ├─ control.lua ├─ changelog.txt ├─ thumbnail.png └─ locale/en/locale.cfg ```