Skip to main content
Version: Next

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

HookConfig locationWhen it runsCan block?Extra context
on_scan[readers.scan]After a token is scanned, before the token or mapping script runsYes, blocks token processingscanned
on_remove[readers.scan]After a token is removed in hold modeYes, blocks remove processing and keeps media runningnone
before_exit[[systems.default]]Before media for the matching system stops or is replacedNo, errors are logged; capped at 30 secondsnone
before_media_start[launchers]Before a media-launching command runsYes, blocks launchlaunching
on_media_start[launchers]After active media is setNo, errors are loggedactive media
on_boot[service]First Core start after the device bootsNo, errors are loggedhook
on_ready[service]Every time Core starts and is readyNo, errors are loggedhook

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 as on_boot or on_ready.
  • hook.first_boot_start: true when this Core start is the first start for the current OS boot.
  • scanned: available in on_scan.
  • launching: available in before_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"