#Adding a game

Every game lives in its own directory under games/<slug>/, fully isolated from the others: its own dependencies, its own toolchain, its own build. The only contract is game.json.

New to the repo? Getting started gets the arcade running first. Want an AI to write the game? Build a game with AI.

#1. Scaffold

npm run new-game -- my-game --template vite      # Vite + TypeScript
npm run new-game -- my-game --template vanilla   # plain HTML/JS, no build step

The slug becomes the directory name and the URL: the game is served at /games/my-game/. Lowercase letters, digits, and hyphens only.

#2. Fill in game.json

{
  "$schema": "../../schema/game.schema.json",   // editor autocomplete + validation
  "name": "My Game",
  "slug": "my-game",                            // must equal the directory name
  "description": "Shown on the arcade landing page.",
  "authors": ["Your Name"],                     // display names, shown on the game card
  "thumbnail": "thumbnail.png",                 // optional, 16:9 recommended
  "addedAt": "2026-06-12",
  "status": "live",                             // "hidden" = deployed but not listed
  "genre": "Action",                            // free text, builds the arcade's genre filter
  "navBar": "expanded",                         // "collapsed" parks the arcade bar behind a handle
  "build": {
    "command": "npm install && npm run build",  // run with cwd = your game dir
    "outputDir": "dist"                         // deployed verbatim to /games/my-game/
  },
  "version": "1.0.0",                           // optional, shown on the game card
  "updatedAt": "2026-06-21",                    // optional, shown on the game card
  // Informational landing-page chips. Any of: singlePlayer, multiplayer, invites, achievements, leaderboard.
  // (achievements is set for you when you ship an achievements.json; see §5.)
  "features": { "singlePlayer": true, "leaderboard": true },
  // Optional: named leaderboards rendered on the arcade profile (your backend writes them; see §6).
  "leaderboards": [{ "id": "score", "name": "Top Score" }]
}

Rules enforced at build time:

  • slug must match the directory name.
  • After your build command runs, outputDir must exist and contain an index.html.
  • Every directory under games/ must have a game.json.

#3. Pick any toolchain

  • Node-based (Vite, Phaser, React, whatever): declare your build in build.command. Your package.json/lockfile live inside your game dir and never conflict with other games.
  • Godot / Unity / other exports: export the web build into a folder (e.g. public/), commit it, and set "command": null, "outputDir": "public". CI doesn't need your engine installed, because the export is the artifact. Two export settings do need changing from their defaults, and there's an SDK bridge per engine: shipping an engine export · Godot · Unity.
  • Plain HTML/JS: same as above, with no build, just committed files.

Keep your game's URLs relative (Vite template sets base: "./") so it works when served from /games/<slug>/.

#4. Use the shared SDK (optional)

Load the SDK with one script tag. There is no npm dependency:

<script src="/sdk/v1/arcade.js"></script>
Arcade.init();                                    // slug auto-detected from the URL
await Arcade.leaderboard.submit(1234);            // attributed to the signed-in user
const top = await Arcade.leaderboard.top(10);     // [{ player, score, at }, ...]

const user = await Arcade.auth.getUser();         // { email, name, picture } | null
if (!user) Arcade.auth.login();                   // shared arcade sign-in

Leaderboard calls reject with ArcadeOfflineError when the API is unreachable, so catch and carry on.

Multiplayer? The same SDK also gives you realtime rooms + presence (Arcade.realtime), player-to-player invites (Arcade.invites), a player typeahead (Arcade.players), and a drop-in notifications bell (Arcade.notifications.mount).

Details: accounts · leaderboards · achievements · multiplayer.

#5. (Optional) Add achievements

Drop an achievements.json beside your game.json, worth up to 1000 GS (Gamesight Score) spread across your achievements, then unlock them with one call:

await Arcade.achievements.unlock("first-flight");   // no-op if already earned

The popup, the chime, the player's score, and the browse page at /achievements/<slug>/ all come for free. Regenerate the API's copy with npm run gen-achievements after editing the file. Full guide: achievements.

#6. (Optional) Give your game its own backend

Need persistent data (saves, collections, progress), or server-authoritative multiplayer? Declare a backend block in game.json and add a server module: the arcade provisions your DynamoDB tables and routes /api/<slug>/* to you. The same module can also export realtimeMatch(ctx, data) to validate + apply PvP moves server-side (and write named leaderboards). Full walkthroughs: saving game data · multiplayer.

#7. Test the whole arcade locally

npm run build      # builds SDK + every game + landing page into dist/
npm run preview    # http://localhost:8002 (auto-increments if busy), production-style routing + /api

Your game should appear on the landing page and be playable at http://localhost:8002/games/my-game/. Sign-in works locally with no Google; see accounts.

#8. Ship it

Open a PR against main from a game/<slug> branch. CI runs validation and a build check with no AWS access. Anthony Kinson or Robert Syvarth reviews every game before it goes live; merging deploys automatically. Full walkthrough: submitting your game.

How it all works underneath: architecture and ../infra/README.md.

View docs/adding-a-game.md on GitHub ↗