Developer guide
Everything your game needs to work great on Tapverse.
What to upload
- A single .html file, or a .zip with an index.html at the top.
- Up to 50 MB zipped, 200 MB unzipped, 1000 files.
- Use relative paths for your files (assets/hero.png, not /assets/hero.png from another site).
- Loading libraries from CDNs is fine.
Engines and tools
Anything that exports to the web works. Recommended settings for popular engines:
- AI (ChatGPT, Claude, Gemini, Rosebud…)
- Ask for a single HTML file with everything inside. Upload it as is.
- Phaser, PixiJS, Three.js, Babylon.js, p5.js, Kaplay
- Zip your build folder with index.html at the top.
- Unity
- Build for WebGL. Brotli or Gzip compression both work; keep the build under 50 MB zipped.
- Godot 4
- Export for Web with Thread Support turned off (the default since 4.3). Godot 3 works as is.
- GameMaker, Construct 3, GDevelop, PlayCanvas
- Use the HTML5 / Web export and zip the output folder.
- Defold, Rust/Bevy, C++/Emscripten, Pygame (pygbag)
- Any WebAssembly build works; include the .wasm files in the zip.
- RPG Maker MV/MZ, PICO-8, Twine, Ren'Py, Scratch (TurboWarp)
- Use their web/HTML export.
Not supported: Android APKs, iOS apps, Windows or Mac executables, and multithreaded builds that need SharedArrayBuffer.
Camera and microphone
Tick them when you upload a version and tell players why. Your game only gets access to what you declare, and players are asked before it loads.
Invite links (Tapverse SDK)
Inside Tapverse your game runs on its own address, so location.href isn't a link your friends can open. Add the SDK and Tapverse builds invite links for you:
<script src="/_tapverse/sdk.js"></script>// Room code from an invite link, e.g. /g/your-game?room=ABCD
const room = Tapverse.params.get("room");
// Invite button: opens the share sheet with a Tapverse link to this room
inviteButton.onclick = async () => {
if (Tapverse.inside) {
await Tapverse.share({ params: { room: "ABCD" }, text: "Play with me!" });
}
};
// Or just get the link
const link = await Tapverse.inviteLink({ room: "ABCD" });Anything you pass in params comes back in Tapverse.params when a friend opens the link. Up to 10 params; names use letters, numbers, - and _.
Multiplayer rooms
No server needed. Tapverse hosts real-time rooms for your game: players join with a code or an invite link, and you send messages and shared state between them.
// Create a room (or join one: Tapverse.rooms.join("ABCD"))
const room = await Tapverse.rooms.join(Tapverse.params.get("room"), { name: "Ana" });
room.code; // "K7P2Q", share it with room.invite()
room.players; // [{ id, name }], in the order they joined
room.isHost; // the longest-connected player is the host
room.on("join", (player) => {});
room.on("leave", (player) => {});
room.on("host", (hostId) => {}); // the host left, someone new is in charge
room.on("message", (data, from) => {}); // from room.send
room.on("state", (state, fromId) => {}); // from room.setState
room.send({ x: 10, y: 20 }); // to everyone else
room.send({ secret: true }, player.id); // to one player
room.setState({ turn: "B", scores: { a: 3 } }); // shared state, late joiners get it too
room.invite("Join my game!"); // share sheet with a link to this room- Up to 16 players per room. Messages are JSON, up to 16 KB.
- About 30 messages a second per player: batch fast updates (e.g. send positions every 50–100 ms).
- Shared state holds up to 64 KB and is cleared when everyone leaves.
- Player names are typed by players: show them with textContent, never innerHTML.
- Dropped connections retry on their own for about 20 seconds, keeping the player's id. Listen for reconnecting and reconnected to show it; close means they're out.
Ads and rewards
Tapverse shows ads around your game (never inside it) and shares the revenue with creators. You can also offer players a reward for watching an ad: extra lives, coins, a second chance…
// A reward for watching an ad (e.g. continue after losing)
if (await Tapverse.ads.rewarded()) {
player.lives += 1;
}
// A short ad between levels
await Tapverse.ads.interstitial();
startNextLevel();- rewarded() resolves true only if the player watched the whole ad. Give the reward only then.
- interstitial() is a hint: Tapverse may skip it to avoid showing too many ads. Call it at natural breaks, like between levels.
- Outside Tapverse both resolve right away with no ad, so your game works everywhere.
- Pause your game's sound and action while awaiting them.
Building with AI?
Paste this to your AI assistant:
My game will be published on Tapverse. Add the Tapverse SDK with
<script src="/_tapverse/sdk.js"></script> in the <head>.
Read the room code on startup with Tapverse.params.get("room").
For online multiplayer, use Tapverse rooms instead of your own server:
const room = await Tapverse.rooms.join(Tapverse.params.get("room"), { name });
then room.send(data), room.setState(patch), room.on("message" | "state" | "join" | "leave" | "host", fn)
and room.invite() for the "Invite friends" button.
Limits: 16 players, 16 KB JSON messages, ~30 messages/second per player.
For a "watch an ad to continue" button, give the reward only if await Tapverse.ads.rewarded() is true,
and call await Tapverse.ads.interstitial() between levels. Pause sound and gameplay while they run.