For developers · @camtable/sdk

Build a game that plays inside a video call.

Your rules, your hosting. The table, the seats, the cameras and the whispers are CamTable's: your game page opens over a Discord Web call, with your friends' cameras in it.

  • One package: @camtable/sdk
  • Nothing to upload, no review
  • Host mode costs nothing
The layers of a call
  1. Join Consent CamTable Players see your permissions before they join.
  2. Tile HUD CamTable Scores, turns and whispers on the tiles. z-index 95000
  3. Your game page You Players' cameras sit in your video slots. z-index 90000
  4. The Discord Web call Discord The video grid and controls stay put.

Top to bottom. A click on your page's empty space goes through to the call.

Quick start

Scaffold it, play it in the Inspector.

Coming to npm @camtable/sdk and create-camtable-addon arrive on npm with the public beta. These are the commands you'll run then; the guide is ready now.

Terminal
pnpm create camtable-addon my-game            # host mode: static files only, free to host
pnpm create camtable-addon my-game --engine   # engine mode: one Worker serves the pages and your engine
cd my-game
pnpm install
pnpm dev                                      # then open the Inspector link it prints
  1. Pick a template

    Without a flag you get a host-mode project: static files only. With --engine, your pages and your engine share one Cloudflare Worker.

  2. Run pnpm dev

    Your addon goes behind a temporary https tunnel, and the command prints an Inspector link with your manifest filled in, plus an Install Link for a real call.

  3. Play every seat

    Sign in to the Inspector with Discord, open a Test Room and play every player of the match yourself.

What pnpm dev prints
  Manifest URL  https://<random-words>.trycloudflare.com/manifest.json
  Inspector     https://camtable.app/inspector?url=<manifest url>
  Install link  https://camtable.app/install?url=<manifest url>

There is no offline way to try a game: the room runs on CamTable's server and reaches your addon only over https. That's what the tunnel is for. What runs offline is your tests.

What the scaffold contains

src/game.ts
The rules, as a pure reducer. The same object runs in both modes.
src/addon.ts
The manifest and every protocol route. Change ADDON_ID first.
index.html, src/main.ts
The game page: Tap Race, first to ten taps wins.
tests/
The reducer, a whole match and every route. None needs a network.

Code

Write the rules once.

Straight from the SDK's README: the game page starts the SDK, and the rules run in the page of the player who opened the room. That's host mode; no engine needed.

Game page · host mode
import { CamTableSDK, createHostAuthority, rng } from "@camtable/sdk";

const sdk = new CamTableSDK();
createHostAuthority(
  {
    apply: ({ state, action, playerId }) => {
      const roll = rng(`${state.seed}:${state.moves}`).int(1, 6); // reproducible, not secret
      return { state: { ...state, moves: state.moves + 1, roll } };
    },
  },
  { sdk },
);
await sdk.init();
createHostAuthority
Runs your reducer in the host's page and commits the reply to the room. On any other player's page it ignores intents, so you call it unconditionally.
apply
State, action and the acting player go in; { state, views?, rejected?, timers?, gameOver? } comes out. The reply is the same in both modes.
rng(seed)
A seeded generator. A reducer never reads the clock or Math.random: keep the seed in the state, and the same request gives the same reply anywhere.
sdk.init()
The page knows it is the host only once init has named its local player. Opened outside a room, it rejects with a NotInRoomError that points to the Inspector.

The rest of the page is two calls: draw what the room broadcasts with sdk.onStateChange, and send what the player did with sdk.sendAction. A move is always an intent, never a state. A reducer that takes longer than 2.5 seconds, throws or answers something else is refused, so the next intent is never held up.

Host mode and engine mode

Where do the rules run? One line decides.

If the Play Option declares an engine, the game runs in engine mode; if not, in host mode. There is no other switch. The reducer is the same in both, so moving between them is a configuration change.

Host mode

Default
Who runs the rules
The browser of the room host, the player who opened the room.
Who can cheat
Only the host. Everyone else just sends intents.
Hidden information
The host sees all of it; other players see only their own view.
Randomness
The host's: rng is reproducible, not fair.
What you host
Static files only.
Your cost
Nothing. Static files are free and unlimited on Cloudflare.
Join Consent says
Rules run in the browser of the player who opened the room (they can see hidden information)

Host mode for a casual game between friends.

Engine mode

engine.url
Who runs the rules
Your server, which CamTable's room calls over signed HTTP.
Who can cheat
Nobody, the host included.
Hidden information
Hidden from everyone.
Randomness
Yours: the host can't influence it.
What you host
Your pages and an engine. The template makes them one Worker.
Your cost
One engine call per move or timer. Cloudflare's free plan allows 100,000 Worker invocations a day.
Join Consent says
Rules run on <your engine origin>

Engine mode for a hidden hand, a secret role or a game with stakes.

CamTable's own Official Addon is built on this package and comes through the same protocol, with no privileged path: 14 of its games run in engine mode, Dice Duel in host mode.

What the SDK gives you

The hard parts are CamTable's.

CamTable carries the hard parts of running a game in a call; you make a few calls. Names, tiles, whispers, the microphone and the camera open only with a permission your manifest declares.

  • Seats and players

    Permission: players.profile

    sdk.init() gives you the room, the local player and everyone at the table. Each player is a per-match Match Player Id (mp_…); no Discord id, server or channel ever reaches your game. Names and avatars need a permission.

  • Video slots

    Mark an element with a player's id (data-camtable-video) and CamTable draws that player's camera right in its box, over your page. You never touch the pixels.

  • Tile overlays

    Permission: tiles.overlay

    Put badges, counters, timers, a status line and buttons on the players' call tiles. When someone presses a tile's button, your page hears it through onTileAction.

  • Whispers

    Permission: whisper

    Your rules name groups, or your page makes Whisper Requests for its player. The voice goes browser to browser. You learn who whispers with whom, never what is said.

  • Call Mute and Call Cover

    Permissions: call.mute , call.cover

    Close a player's voice to the call for a night phase (setCallMuted), and cover cameras with a dark frame, a blur or the last frame through callCover in your state. The player can't lift it while the match runs, and only they are told.

  • Game Settings and the Lobby

    Declare fields (number, toggle, choice, multi-choice) and CamTable draws the form. The host picks in the Lobby before each match, and your rules get the picks as settings on every request. Up to 24 fields per Play Option.

  • Custom tabs

    Extra pages, such as stats or a match history, open in a drawer from the game's top bar. The game and its tabs talk through tab state that stays in the player's browser.

  • Layers and pointer pass-through

    Your page is a transparent layer over the call: empty space passes clicks through to Discord, and only your widgets take them. The SDK measures your buttons and other interactive elements itself; for a canvas, you register the regions.

Inspector and Test Rooms

Play every seat yourself, no call needed.

The Inspector is a page on the CamTable website. Paste your addon's https manifest address, pick a game and a Play Option, and open a Test Room. No Discord call, no extension: signing in with Discord is enough.

  • A real room on the real server. The Lobby, Game Settings, the room's limits and the blocklist work as in any room, in both modes.
  • Every seat is its own game frame, with its own room connection, its own Match Player Id and a placeholder video tile. The first seat is the host.
  • In engine mode the room calls your engine with CamTable's real signature. Each request carries "test": true, so an engine that keeps stats can leave the match out.
  • The event log shows every bridge call each seat's page made, beside the actions your rules refused.
  • No paid Plan needed. Only you can join, it is never listed, and it closes after 30 minutes. You can have one open at a time.
Illustration: a Test Room in the Inspector with three seats. Each seat shows its own game frame, with a placeholder video tile for every player under it.

Security model

It runs in a sealed box and speaks by permission.

Players run your code next to their own Discord session, so the boundaries are drawn up front.

  • Opaque sandbox

    Your game page opens in a sandbox="allow-scripts" frame with an opaque origin. It can't read Discord's page, cookies or storage, and it can't reach the extension's APIs. Camera pixels and microphone audio never reach it.

  • Permissions

    Every capability of the bridge needs a permission your manifest declared. A call you didn't declare is refused with PERMISSION_DENIED. Ask for little: every permission is a line players read before they decide.

  • Join Consent

    Before joining, a player sees your addon's name, its origin, its permissions and who runs the rules, and that CamTable neither hosts nor reviews it. A player who declines stays in the call; the game just doesn't open for them.

  • Blocklist

    An addon on CamTable's remote blocklist can't be installed, started or joined, and its engine is never called. Accepted notices from rights holders become entries.

The addon protocol

No store in the middle. Your addon lives at your address.

An addon is a service you host that describes itself in a manifest.json. The routes follow Stremio's scheme; every route must answer CORS for all origins, and all of it can be static files.

The protocol's routes
GET  /manifest.json                          -> the manifest
GET  /catalog/game/{catalogId}.json          -> { metas: [...] }
GET  /meta/game/{id}.json                    -> { meta }
GET  /play/game/{id}.json                    -> { plays: [...] }
POST {engine.url}/apply                      -> engine reply    (engine mode only)
POST {engine.url}/timeout                    -> engine reply    (engine mode only)
Manifest
Id, version, the protocol version camtable: "1", resources, Catalogs and permissions. CamTable validates it at install; validateManifest runs the same check on your side.
Catalogs
Each Catalog is a row in Keşfet, the panel's Discover tab. You decide which games stand in it and in what order; CamTable ranks nothing. The genre and player-count filters are yours too.
Play Options
One way to play a game: your game page's address, player counts and permissions, and if you like an engine, custom tabs and Game Settings. Each one becomes a Play button on the game's page.

A decentralised platform

  • Your code stays on your server: CamTable's extension and server never download, store or run it. CamTable never reviews, lists or recommends community addons.
  • Players add yours by its manifest URL, or by an Install Link you share.
  • The only money channel is the donationUrl in your manifest. CamTable takes no revenue share.
  • A room pins the version it opened with for the whole match: its game page address, tabs, permissions and engine URL included.

Rules and limits

You're the publisher. The limits are written down.

  • You are the publisher

    CamTable doesn't host, review, list or promote community addons.

  • Don't use what you don't own

    Game mechanics are generally not protected, but names, text and art are. Don't name your game after a commercial game or use its art. An addon that draws complaints becomes a blocklist entry.

  • Don't collect what you don't need

    You can't see Discord ids, servers or channels. Don't try to work around that.

  • Be quick and available

    A slow or broken addon only hurts its own games, but it does hurt them.

Limits

3 s
Engine response time, and the host's time to commit an intent.
64 KB
State and views, each, serialized.
16
Timers one answer can ask for.
24
Game Settings fields per Play Option.
2–6
Players in a Whisper group your rules name.
15 min
How long a match waits when the host drops; it ends if they don't return.

The table is set. You write the rules.

The guide covers everything from the protocol to hosting, one chapter at a time.

Play it in a real call

Install Links and Discord calls need the CamTable extension.

Add to Chrome Chrome Web Store