Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

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

KeyTypeDefaultEffect
idstring—Unique id ([a-z0-9_]+)
entrypath string—Path relative to the project root
runtime_minstring—Optional player version floor; skip load if player is older

Where this lives

IdeaFilePath
Declarationgame.jsonextensions (array)
Entry scriptproject treepath 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 and ext_* 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 entry script and the other files in that entry’s folder (and everything under extensions/ by convention). Keep companion textures/sfx next to main.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 Extensions node; your instance is named ext_<id>.

ExtensionHost methods

MethodEffect
extension_id() -> StringThis entry’s id
runtime_version() -> StringPlayer / kit version
get_plane(plane_id: String) -> CanvasLayerNamed 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) -> VariantRead 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) -> boolPush 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.

RuleMeaning
Declared array orderLoad order and save-registration order
Unique idSchema rejects duplicates
Save keysAlways ext.<id>.… — isolated per extension
Shared planesChildren stack in load order; use z_index or reorder the array
FlagsNot namespaced — shared with the story. Prefer ext_<id>_* for private state
One failureA skipped entry (missing file, bad script, runtime_min) does not block later entries
CooperationNo inter-extension handles — use flags

Failure modes

ProblemResult
Missing / empty entry fileSkip + error log
GDScript parse / reload errorSkip + error log
Missing vn_extension_setup or not a NodeSkip + error log
Duplicate idKeep first; skip later
runtime_min newer than playerSkip + error log
Author modeAfter title is up: toast above the title listing loaded id(s) (“unrestricted code”)
Production / shippedSilent load

Native extensions · game.json map · Screens · Build and hand off