tutorial
Building apps for Zom-Boy: an API for Project Zomboid modders
Zom-Boy is a retrofuturistic wrist-mounted survival computer for Project Zomboid Build 42. Its terminal includes Zones, perimeter sensors, radar, weather information, and other tools, but I do not want every possible idea to become another built-in Zom-Boy feature.
Instead, Zom-Boy 0.2 introduces a public API and an APPS expansion bus. Another mod can install its own page in the terminal, respond to Zom-Boy events, and read current Zone and sensor information without importing internal UI or server modules.
This makes Zom-Boy useful as a shared interface. A radio mod could display channel status, a vehicle mod could add a tracker, or a medical mod could present its data as another application on the device.
Create a minimal Zom-Boy application
First, declare Zom-Boy as a dependency in the addon's mod.info:
name=My Zom-Boy App
id=MyZomBoyApp
description=An example application for the Zom-Boy terminal.
modversion=1.0.0
versionMin=42.0
require=ZomBoy
The client Lua file can then load the public API and register a terminal page:
require "ZomBoy_API"
if not ZomBoyAPI.hasCapability("terminal.pages") then
return
end
ZomBoyAPI.registerTerminalPage("MyZomBoyApp.status", {
title = "STATUS",
order = 50,
render = function(panel, context)
local zone = context.api.getPrimaryZone()
local label = zone and zone.name or "UNREGISTERED"
panel:drawText(
"ZONE // " .. label,
context.x,
context.y,
context.green.r,
context.green.g,
context.green.b,
1,
UIFont.Medium
)
end,
})
When both mods are enabled, STATUS appears in Zom-Boy's APPS tab. The page receives its drawable area, player, API reference, and the terminal's primary and dim colors through context, allowing the extension to fit the existing display without reaching into Zom-Boy's UI implementation.
Registration IDs must be namespaced. An ID such as MyZomBoyApp.status prevents independently developed addons from claiming the same name. Duplicate or invalid registrations fail safely and write a diagnostic to the console.
Zom-Boy currently displays the first six registered applications, ordered by the order value and then by ID. Titles are limited to 18 characters so they fit the terminal navigation.
Respond to events
An application does not need to poll Zom-Boy continuously. It can subscribe to events and update its presentation when something relevant changes:
ZomBoyAPI.on(
"ZomBoy.zoneChanged",
"MyZomBoyApp.zoneTrace",
function(payload)
local name = payload.zone and payload.zone.name or "NONE"
print("[MyZomBoyApp] current Zone: " .. tostring(name))
end
)
The listener ID is also namespaced. If the addon no longer needs the listener, it can remove it explicitly:
ZomBoyAPI.off("ZomBoy.zoneChanged", "MyZomBoyApp.zoneTrace")
API 2.0 exposes events for:
- API readiness;
- opening and closing the terminal;
- changing terminal tabs;
- registering terminal pages;
- changing the selected Zone;
- changing sensor state.
Each listener receives its own copy of the payload. A broken listener is logged without preventing the remaining listeners from running.
Read Zone and sensor snapshots
The public data methods are deliberately read-only:
local zone = ZomBoyAPI.getPrimaryZone()
local sensors = ZomBoyAPI.getSensors()
print("Zone: " .. tostring(zone and zone.name or "UNREGISTERED"))
print("Sensors: " .. tostring(#sensors))
These values are presentation snapshots. An extension may use them to render information, but it should not mutate Zom-Boy internals or write directly to player ModData. Persistent gameplay changes remain owned by Zom-Boy's server-side command path.
That boundary is important for multiplayer compatibility: the terminal can expose useful information without turning a client-side application into the authority for shared world state.
Check capabilities, not mod releases
The API version is independent of the Zom-Boy mod version. An addon can inspect both:
local apiVersion, modVersion = ZomBoyAPI.getVersion()
print("Zom-Boy API: " .. tostring(apiVersion))
print("Zom-Boy mod: " .. tostring(modVersion))
For feature detection, prefer capability checks:
if ZomBoyAPI.hasCapability("sensors.read") then
local sensors = ZomBoyAPI.getSensors()
end
This allows the public surface to grow without forcing every addon to encode a list of compatible Zom-Boy releases. Existing functions and callback fields will not be removed without a new major API version.
Start with the demo addon
The Zom-Boy repository includes a deliberately small standalone addon in examples/ZomBoyAPIDemo. Enable it alongside Zom-Boy and an API TEST application appears in the terminal, showing the API version, current Zone, and sensor count.
The complete integration contract is in the Zom-Boy API documentation. Extensions should depend on the ZomBoy mod and require ZomBoy_API; internal client, shared, and server modules are implementation details and may change.
The API is intentionally small for now. I would rather establish a stable integration point and expand it around real addons than expose every internal function. If you build something for the Zom-Boy terminal, I would like to hear what your mod needs from the expansion bus next.