extensions (game.json)
Declare native extensions. Each entry is a GDScript file under your project that the player loads at startup (and reloads on Author Refresh in author mode).
How the extension lives in the player (pause, planes, input, lifecycle, recipes): Native extensions. This page is the declaration + Host method reference.
{
"extensions": [
{
"id": "mouse_parallax",
"entry": "extensions/mouse_parallax/main.gd",
"runtime_min": "0.1.0"
}
]
}
Usually you need
| Key | Type | Default | Effect |
|---|---|---|---|
id | string | — | Unique id ([a-z0-9_]+) |
entry | path string | — | Path relative to the project root |
runtime_min | string | — | Optional player version floor; skip load if player is older |
Where this lives
| Idea | File | Path |
|---|---|---|
| Declaration | game.json | extensions (array) |
| Entry script | project tree | path in entry |
Concepts: Native extensions. Example: examples/example-06-native-extension/. Map: game.json → extensions.
Entry script
extends Node
func vn_extension_setup(host) -> void:
# required — host is the ExtensionHost API (untyped)
pass
- Cold boot loads every declared entry once.
- Author Refresh (F5) reloads extensions in author mode (frees prior instances, clears
ext.*save keys andext_*command handlers, then loads again). Production / shipped player builds still load once at boot — restart if you only ship a player without author tools. - Player Build (Filter unused assets on) ships the
entryscript and the other files in that entry’s folder (and everything underextensions/by convention). Keep companion textures/sfx next tomain.gd, or turn the filter off. - Reset your own state on story end via
host.connect_story_ended(session end does not free your Node). - Parent is a reserved
Extensionsnode; your instance is namedext_<id>.
ExtensionHost methods
| Method | Effect |
|---|---|
extension_id() -> String | This entry’s id |
runtime_version() -> String | Player / kit version |
get_plane(plane_id: String) -> CanvasLayer | Named draw plane, or null |
register_save(key, save_fn, restore_fn) | Registers under ext.<id>.<key> |
register_command(verb, handler) | Registers story command ext_<id>_<verb> |
get_flag(name) -> Variant | Read a story flag |
set_flag(name, value) | Write a story flag (shared with the story) |
connect_node_entered(cb) | Story entered a node |
connect_flag_changed(cb) | A flag changed |
connect_story_ended(cb) | Story ended |
set_input_blocker(enabled) | Block dialogue advance / choice confirm (not tree pause) |
get_phase() -> String | "title" / "playing" / "exit_commands" / "story_ending" |
jump(node_id) | Request a story jump (same path as jump command) |
resume() | Clear the input blocker only |
notify(message) | Toast above modal screens |
push_screen(screen_id) -> bool | Push an id from screens.json, or false |
pop_screen() | Pop the current modal screen if showing |
log(message) | print with [ext:<id>] prefix |
warn(message) | Warning with the same prefix |
Custom commands
Call host.register_command("burst", handler) to handle Mode A / JSON commands whose type is ext_<id>_burst (host builds the type string; verb must match [a-z0-9_]+). Schema/check allows that type only when id is declared in game.json → extensions[].id. Remaining Mode A tokens land in "args" (and key=value tokens are also copied onto the command object). For structured fields prefer JSON / Mode B.
Plane ids
bg, scene_ui, chars, panel, above_panel, overlay, hud, notify, modal, cursor, transition.
Draw order and title/pause stacking: Native extensions · Screens.
Save order
Core systems register first; extensions register afterward (load order). Restore runs in registration order, so extension state restores after core state. F5 unregisters all ext.* keys before reload.
Using several extensions
There is no limit on how many entries you declare.
| Rule | Meaning |
|---|---|
| Declared array order | Load order and save-registration order |
Unique id | Schema rejects duplicates |
| Save keys | Always ext.<id>.… — isolated per extension |
| Shared planes | Children stack in load order; use z_index or reorder the array |
| Flags | Not namespaced — shared with the story. Prefer ext_<id>_* for private state |
| One failure | A skipped entry (missing file, bad script, runtime_min) does not block later entries |
| Cooperation | No inter-extension handles — use flags |
Failure modes
| Problem | Result |
|---|---|
Missing / empty entry file | Skip + error log |
| GDScript parse / reload error | Skip + error log |
Missing vn_extension_setup or not a Node | Skip + error log |
Duplicate id | Keep first; skip later |
runtime_min newer than player | Skip + error log |
| Author mode | After title is up: toast above the title listing loaded id(s) (“unrestricted code”) |
| Production / shipped | Silent load |
Related
Native extensions · game.json map · Screens · Build and hand off