Hooks
Hooks run ZapScript at specific points in Core's lifecycle. Use them for local automation, webhooks, save workflows, startup actions, lighting, buttons, or scripts that need context from Core.
Hooks are configured in config.toml. Manual config edits require a Core restart or config reload where supported.
Hook reference
| Hook | Config location | When it runs | Can block? | Extra context |
|---|---|---|---|---|
on_scan | [readers.scan] | After a token is scanned, before the token or mapping script runs | Yes, blocks token processing | scanned |
on_remove | [readers.scan] | After a token is removed in hold mode | Yes, blocks remove processing and keeps media running | none |
before_exit | [[systems.default]] | Before media for the matching system stops or is replaced | No, errors are logged; capped at 30 seconds | none |
before_media_start | [launchers] | Before a media-launching command runs | Yes, blocks launch | launching |
on_media_start | [launchers] | After active media is set | No, errors are logged | active media |
on_boot | [service] | First Core start after the device boots | No, errors are logged | hook |
on_ready | [service] | Every time Core starts and is ready | No, errors are logged | hook |
Reader hooks
Reader hooks live under [readers.scan].
[readers.scan]
on_scan = "**http.post:https://example.com/scan,text/plain,Token scanned"
on_remove = "**echo:card was removed"
on_scan runs before the scanned token is processed. If the hook fails, the scan is blocked.
on_remove only runs in hold mode. If the hook fails, remove processing is blocked, so media keeps running.
When ZapScript execution is turned off, for example while the command line or a client is waiting to read or write a token, a scanned token is ignored quietly: no failure sound plays, no failed scan is recorded in history, and reader hooks are not treated as blocked.
Media launch hooks
Media launch hooks live under [launchers].
[launchers]
before_media_start = "**http.get:https://example.com/before-launch"
on_media_start = "**http.get:https://example.com/media-started"
before_media_start runs before launch commands such as **launch, **launch.random, **launch.title, and MiSTer MGL launches. If it fails, the launch is blocked.
on_media_start runs after Core starts media. It is useful for notifications or follow-up actions. If it fails, the launched media keeps running.
Exit hooks
Use before_exit on a system default to run a script just before media for that system stops or is replaced. It runs when another token launches over the running game, on stop, playlist.stop, and the media.stop API method, when a playtime limit stops the game, and on a hold-mode card removal after on_remove and exit_delay. It does not run when a game is quit from the emulator or frontend itself, because the media is already gone by the time Core notices.
[[systems.default]]
system = "SNES"
before_exit = "**input.keyboard:{f12}||**delay:2000"
This can be useful for opening an emulator menu, saving, or giving the platform time to settle before the media exits.
Core waits for the script, up to 30 seconds. A failure is logged and never stops the exit.
Service startup hooks
Service hooks live under [service].
[service]
on_boot = "**execute:/media/fat/zaparoo/scripts/on-boot.sh"
on_ready = "**execute:/media/fat/zaparoo/scripts/on-ready.sh"
on_boot runs once after the device boots. If Core restarts without rebooting the device, on_boot is skipped.
on_ready runs every time Core starts and is ready.
Expression environment
Hook scripts can use the normal ZapScript expression environment. Hook-related fields include:
hook.name: current hook name, such ason_bootoron_ready.hook.first_boot_start:truewhen this Core start is the first start for the current OS boot.scanned: available inon_scan.launching: available inbefore_media_start.
The normal environment also includes media_ready, which is true when active media is considered ready.
Example:
[service]
on_ready = "**echo:Hook [[hook.name]] started, first boot: [[hook.first_boot_start]]"
External scripts
The **execute command receives a ZAPAROO_ENVIRONMENT environment variable containing the expression environment as JSON.
[zapscript]
allow_execute = ["/media/fat/zaparoo/scripts/.*"]
[service]
on_ready = "**execute:/media/fat/zaparoo/scripts/on-ready.sh"
execute still requires a matching allow_execute entry. Input commands still follow the configured [zapscript.input] rules.
Examples
Send a webhook when a token is scanned:
[readers.scan]
on_scan = "**http.post:https://hooks.example.com/zaparoo,text/plain,[[scanned.id]]"
Block launches when an external script returns an error:
[launchers]
before_media_start = "**execute:/media/fat/zaparoo/scripts/check-launch.sh"