1What a Clawcade game is
A Clawcade game is one self-contained HTML file. All your HTML, CSS, JavaScript, and assets
live inside that single file — inline <style> and
<script>, images as data URLs or drawn on a
<canvas>. No build step, no folder of files, no server.
You publish that file, Clawcade hosts it, and you get a permanent play link like
play.clawcade.gg/g/<id> you can share anywhere.
2Publishing
Two ways:
- Web: go to /publish, drag your
.htmlfile in (or paste the HTML), and hit Publish. You get a play link and a one-time edit key — save it, it's the only way to edit the game later. - From your AI: add the Clawcade MCP connector and just ask it to publish — see section 10.
New games start unlisted — playable by anyone with the link, but not shown in the public arcade until you submit them (section 9).
Multi-file games (static ZIP bundles)
Built with Vite, React, Phaser, Three.js, Unity or Godot — i.e. an index.html
plus separate /assets/*.js|css|png|wasm? Don't inline it. Zip your build and
upload the .zip — Clawcade hosts the files as-is and relative paths just work.
- index.html must be at the zip root (one wrapping folder is auto-stripped).
- No build runs on our side — upload an already-built static site (your
dist/). - Limits: 20 MB zip, 60 MB uncompressed, 200 files. Allowed types include
.wasmand common assets;.svgisn't allowed yet. - Web: pick the .zip on /publish. AI: the
get_bundle_upload_urltool (zip -r game.zip dist/then curl).
The injected Clawcade APIs (leaderboard, multiplayer, mobile controls) work in bundles too.
3The sandbox & rules
Your game runs inside <iframe sandbox="allow-scripts allow-pointer-lock"> on a separate
origin from the rest of Clawcade. That keeps everyone safe, and it means a couple of things for you:
- One file only. External URLs may be blocked or slow; inline everything you can.
- Saving progress: just use
localStorage. Real storage is blocked in the sandbox, so Clawcade swaps in its ownlocalStoragethat persists per game on the player's device: reads work at startup, writes are saved automatically, and it survives reloads and new versions of your game. No server needed. Limits: strings only (useJSON.stringify), about 500 KB per game, and it stays on that device and browser (Safari may clear it after ~7 days without a visit). Players get a Delete my save button.sessionStorageresets each visit. Don't usewindow.name; Clawcade uses it to hand your game its save. - No login / no cookies. Players are anonymous; saves belong to the device, not an account.
- Talk to Clawcade via
postMessage(for leaderboards) or the injectedClawcadeobject (for multiplayer). Both are described below.
4Leaderboards
Any game can have a leaderboard with one line of code. When the player's score or result is final, post it to the page:
// N is a number. name is optional (the page will ask if you omit it).
parent.postMessage({ clawcade: 'score', score: N, name: 'optional' }, '*');
Then name the metric when you publish or edit — e.g. "Max round", "Best time", "Max jumps" — and choose the order:
- high (default) — bigger is better (points, distance).
- low — smaller is better (time, strokes).
Clawcade shows a top-10 panel under the game (best score per name). Scores are client-reported and anonymous — fun, not cheat-proof.
5Multiplayer
Clawcade gives every game a global Clawcade object for invite-code lobbies:
one player hosts a room and gets a short code, friends join with it, and you exchange messages.
There is no server for you to run — Clawcade relays the messages for you.
The whole API
| Call | What it does |
|---|---|
await Clawcade.host() | Open a new room. Resolves to { code, id, isHost:true } — show code to a friend. |
await Clawcade.join(code) | Join an existing room. Resolves to { id, isHost:false }, or rejects if the code is wrong/full. |
Clawcade.send(obj) | Broadcast any JSON obj to everyone else in the room. |
Clawcade.on(event, cb) | Listen for an event (see below). |
Clawcade.leave() | Leave the room. |
Clawcade.invitedRoom | The room code from a shared #room=… link, or null. |
Events you can listen for
| Event | Callback gets | Meaning |
|---|---|---|
'message' | { from, data } | Another player sent data (what they passed to send()); from is their id. |
'peers' | [ {id, host}, … ] | The roster changed (someone joined or left). Use its length to know when to start. |
'host' | — | You were promoted to host (the host left, or didn't come back within 8 s). |
'open' | { peerId, isHost, code } | Your connection is live (also after every reconnect). Set your role from isHost: it can change while you were away. |
'close' | — | You left, the room is full or doesn't exist, or a dropped connection couldn't get back in within 2 minutes. |
'error' | { error } | Hosting or joining failed, or a message was too big (each send() must be ≤ 8 KB of JSON). |
'invite' | code | The player arrived via a shared link carrying a room code — auto-join with it. |
'reconnecting' | { attempt } | The connection dropped and Clawcade is getting you back into the same room (show a "reconnecting…" hint). |
'reconnected' | { peerId, isHost, code } | Back in, with the same Clawcade.playerId. The host should re-send the full state. |
A minimal example
// --- One player hosts ---
const room = await Clawcade.host(); // { code:'ABCD', id, isHost:true }
showCodeToFriend(room.code);
// --- The friend joins (from their screen) ---
await Clawcade.join('ABCD');
// --- Start once both players are present ---
Clawcade.on('peers', function (list) {
if (list.length === 2) startGame();
});
// --- Receive messages from the other player ---
Clawcade.on('message', function (m) {
// m.from = sender id, m.data = whatever they sent
applyUpdate(m.data);
});
// --- Send to everyone else ---
Clawcade.send({ paddleY: myPaddle.y });
The host-authoritative pattern (recommended)
For anything with shared state (a ball, a board, scores), don't let each side simulate independently — they'll drift. Instead:
- The host runs the real simulation each tick and broadcasts the state:
Clawcade.send({ ball, scores }). - Guests send only their input (
Clawcade.send({ paddleY })) and render whatever the host last sent. - If you get a
'host'event, you just became the authority — start simulating.
Smooth movement (interpolation) · required for real-time games
Clawcade.smoothing().State arrives in bursts (~20–30 times a second), so drawing the newest snapshot directly looks steppy. The built-in interpolator smooths positions — render a hair in the past (default 100 ms) and lerp between the two surrounding snapshots.
- Positions, not existence. Decide which entities exist from the latest message; use the
buffer only to smooth each one's x/y. Don't ask
get()whether something exists. - One shape per id.
get()returns the newest snapshot's fields, so if you push two shapes into one id (a player update and an enemy list), the last one wins and the other's fields disappear that frame. Give each thing its own id. get(id)returnsnulluntil that id has a snapshot — null-check it. PutrequestAnimationFrameat the top of your loop so one thrown frame can't kill rendering.- Host sends full state each tick (idempotent), not one-off "spawn" deltas — a dropped message then self-heals on the next tick instead of leaving a permanent hole.
var enemyBuf = Clawcade.smoothing({ delay: 100, angles: ['angle'] });
// HOST, every tick: broadcast the WHOLE list, each with a stable id
Clawcade.send({ t: 'state', enemies: enemies.map(e => ({ id: e.id, x: e.x, y: e.y, angle: e.a })) });
// GUEST: existence from the message, positions from the buffer
var liveIds = [];
Clawcade.on('message', function (m) {
if (!m.data || m.data.t !== 'state') return;
liveIds = m.data.enemies.map(e => e.id); // who exists right now
m.data.enemies.forEach(e => enemyBuf.push(e.id, e)); // one buffer per id, one shape
});
// render loop:
liveIds.forEach(function (id) {
var s = enemyBuf.get(id); // null until first snapshot — guard it
if (s) drawEnemy(s.x, s.y, s.angle);
});
A single moving thing (one opponent) can skip ids: buf.push(data) /
buf.get(). Names in angles use shortest-path angular lerp
(no spinning past π). Bare helpers Clawcade.lerp(a,b,t) /
Clawcade.lerpAngle(a,b,t). Opt-in — only worth it for continuous movement.
Reconnection & keeping state
Phones drop connections all the time (a network switch, a screen-lock, a tunnel). Clawcade now
reconnects automatically — you don't manage sockets. There's also a keepalive so dead connections
are noticed fast. Each player has a stable id that survives the reconnect:
Clawcade.playerId, which is also the from on their messages.
- Key every player by
Clawcade.playerId— never by join order or a per-spawn id. The host keeps each player's state in a map under that id. - On disconnect, don't kill them — hold their character for a grace period (~20s) instead of letting it die.
- On reconnect, re-attach them to that held character and resync their loadout, rather than spawning fresh.
Want a "reconnecting…" overlay on the guest? Listen for
Clawcade.on('reconnecting', cb). (If a game has frozen entirely, the play page also
shows its own always-responsive Reload/Exit buttons — that part is automatic.)
Sharing & auto-join
When a game hosts a room, the play-page URL gains #room=CODE. Share that link and
the friend's game can auto-join:
Clawcade.on('invite', function (code) {
if (code) Clawcade.join(code);
});
clawcade_guide topic server). A working relay example ships as the
CLAWPONG sample (host-authoritative 2-player Pong).6Mobile controls
Clawcade can show an on-screen gamepad on phones and tablets and translate it into the same keyboard events your game already listens for. So a keyboard game becomes touch-playable with one line — or zero, if the player taps the 🎮 button.
If your game uses arrow keys (or WASD)
Just declare a D-pad. It maps the four directions to the arrow keys, so your existing
keydown handlers work unchanged:
if (window.Clawcade) Clawcade.controls({ dpad: true });
Want WASD instead, or custom direction keys? Pass codes:
Clawcade.controls({ dpad: { up:'KeyW', down:'KeyS', left:'KeyA', right:'KeyD' } }).
Add action buttons
List up to ten buttons; each maps to a key. Set dpad:false if you only need buttons:
Clawcade.controls({
dpad: true,
buttons: [
{ label: 'JUMP', key: 'Space' },
{ label: 'FIRE', key: 'KeyX' }
]
});
Key names are standard code values:
'ArrowUp', 'Space', 'Enter',
'KeyA'…'KeyZ'.
If your game doesn't use the keyboard at all
Handle presses directly — you get { id, label, key, phase } where
phase is 'down' or 'up'. You can
also poll a button any frame with Clawcade.pressed('fire'):
Clawcade.onButton(function (b) {
if (b.id === 'a' && b.phase === 'down') jump();
});
Analog joysticks (twin-stick / aim)
Discrete keys can't express "aim 37° that way." For movement + free aiming (top-down shooters, twin-stick), declare analog sticks — Clawcade renders them; you read their values each frame:
// left stick = move, right stick = aim
Clawcade.controls({ move: 'joystick', aim: 'joystick' });
// then in your game loop:
var mv = Clawcade.stick('move'); // { x, y, angle, magnitude, active }
player.x += mv.x * speed * dt; // x,y are -1..1 (y is +down, like canvas)
player.y += mv.y * speed * dt;
var aim = Clawcade.stick('aim');
if (aim.active) { facing = aim.angle; shoot(facing); } // auto-fire while aiming
stick(id) returns { x, y, angle, magnitude, active }:
x/y are −1…1, angle is in
radians (0 = right, increasing clockwise — drop it straight into Math.cos/Math.sin),
magnitude is 0…1, and active is whether it's being held
(when released, angle keeps the last facing). You can mix sticks, a D-pad, and
buttons in one controls() call.
Clawcade.stick('aim') instead. The Claw Survivor sample is a
full twin-stick example you can copy.The joysticks are floating & Brawl-Stars-style: each takes a whole half of the
screen, appears under the thumb, and the aim angle keeps tracking your finger even outside the ring
(small dead zone at the center). No game code needed — it's all behind the same stick() API.
Clawcade.showControls(false) when you show a menu and
Clawcade.showControls(true) when play starts.Clawcade.controls(false) so it doesn't get in the way.Even without any of this, touch players get a 🎮 toggle that summons a default
arrows + A/B pad — so most keyboard games on Clawcade are already iPad-friendly. Reference:
the Claw Snake sample (arrow-key game, one line of controls()).
7Cover images
Add a cover image so your game looks good in the arcade and in link previews. On
/publish (or /edit) pick an image —
png, jpeg, webp, or gif, up to 2 MB. From your AI, use the
get_thumbnail_upload_url tool (it needs the game's edit key).
8Editing & the recheck
Every game has a secret edit key (shown once when you publish). With it you can change the HTML, title, leaderboard, or cover at the /edit page — the play link never changes.
When you edit a public game, add a short "what changed" note (a changelog, up to 600 characters).
The reviewer sees it, which makes approvals faster. In the API/MCP this is the
description field.
9Getting into the arcade
Publishing keeps a game unlisted (link-only). To appear on the public
arcade homepage, submit it for review — the button is on your publish
result and the /me dashboard (from your AI, the
submit_to_arcade tool). A moderator approves it, then it's listed. It stays
playable by its link the whole time.
10Publishing from your AI
Any AI can make a Clawcade game. How it publishes depends on what your AI can do:
- Apps with connectors (MCP): Claude, ChatGPT (Plus and up, developer mode), Codex, Cursor, VS Code
Copilot, Gemini CLI, Windsurf/Devin, Cline, Le Chat, Zed. Add the remote server
https://clawcade.gg/mcp(Streamable HTTP, no auth, no API key; legacy SSE at/sse). Then just say "make a game and publish it to Clawcade." Exact steps for each app are on /publish. - Agents with a terminal but no connector: tell them to read
clawcade.gg/llms.txt. It has the full HTTP API (publish, edit, cover,
submit, status) as
curlcommands, plus all the in-game APIs below. - Plain chat AIs (free ChatGPT, Gemini, Copilot, Grok…): ask for the game as one self-contained HTML file, then paste it on /publish.
# Claude Code
claude mcp add --transport http --scope user clawcade https://clawcade.gg/mcp
# OpenAI Codex
codex mcp add clawcade --url https://clawcade.gg/mcp
# Gemini CLI
gemini mcp add --transport http clawcade https://clawcade.gg/mcp
| Tool | What it does |
|---|---|
clawcade_guide | The in-game API reference (leaderboards, multiplayer, touch controls, smoothing) plus a pre-publish checklist. |
publish_game | Publish a small single-file game inline. Returns the link and the one-time edit key. |
get_upload_url | Upload URL for a large HTML build; the agent curls the file so it never passes through the model. |
get_bundle_upload_url | Upload URL for a multi-file .zip build. Pass slug + edit_key to replace an existing bundle. |
edit_game | Change the build, title or leaderboard (needs the edit key). |
get_thumbnail_upload_url | Upload URL for a cover image (needs the edit key). |
submit_to_arcade | Request public listing; a moderator approves it. |
get_game | Status, plays, likes and top scores for any game. Read-only. |
Questions or a broken game? Use the Report this game button under any game. Happy building.
