Build a game
A game is three iframes and one manifest. This page walks the four steps from an empty folder to a running session, with the commands the CLI actually runs.
1. Create
Install the CLI and scaffold a pack. Node 20 or newer. The sample pack you get is the reference implementation for all three iframes.
npm i -g @fzgame/cli fzgame init my-game cd my-game fzgame dev
fzgame dev serves the pack on port 3400 with the kit and a mock runtime, so you can build the game before any server exists.
game/
Player iframe. Draw the game, call the kit.
editor/
Studio iframe. Edits instance config from formSchema.
handler/
Runs on finish. Turns the payload into stored score and result.
fzgame.json
Manifest: identity, entry points, and the four schemas.
2. Ship
Build validates the manifest, vendors the kit into the pack, and zips the three folders. Publish posts that zip as a new version.
fzgame build fzgame publish --api https://gamehub.example.com --token $FZGAME_TOKEN --space <space-id>
Slug plus version is unique on publish: bump info.version in fzgame.json to ship a new pack. A published version is what the catalog lists and what a space instantiates.
3. Integrate
The host page passes the runtime values in the query string. The kit reads them, so the game never hardcodes an API URL.
apiUrl
Gamehub origin the kit talks to.
gameInstanceId
Instance being played.
playerToken
Player identity that already exists on the host.
preview
Runs the session in memory, no server writes.
import { createKit } from '@fzgame/kit'
const kit = createKit()
await kit.load() // GET /pub/game-instances/:id
await kit.connect() // POST /games/runtime/sessions
await kit.emit('ready')
await kit.emit('start')
await kit.setState({ clicks: 1 })
await kit.emit('score', { score: 1 })
await kit.emit('finish', { score: 1, result: { clicks: 1 } })Integration on the product side is one iframe pointed at the instance URL with a player token. Sessions are created on connect() and reused while one is active.
4. Play
A session starts CREATED on connect and moves with named events. Terminal states accept nothing, so a finished session is final.
CREATED -> ready -> READY -> start -> PLAYING <-> PAUSED
finish -> FINISHED
abort -> ABORTEDScores and state live on the session, and kit.getLeaderboard() reads either one row per player or every finished session. Every game ships a leaderboard screen for the instance it runs in.
The full manifest reference and the kit API are in the repository guide at GUIDE.md.
