Publishing on CrazyGames
CrazyGames hosts your build and embeds it in its own pages, and it has a multiplayer surface with
its own rules: invite links between friends, a flag that drops a player straight into a game, and a
running report of which room each player is in. All three are room codes wearing portal clothes, so
the whole integration is @irtio/crazygames plus one origin on your allowed list.
npm install @irtio/crazygames What the portal requires
The portal’s own requirements for multiplayer games are at docs.crazygames.com/requirements/multiplayer, and the SDK reference for every call below is at docs.crazygames.com/sdk/game. The parts that touch irtio:
| The portal asks for | Its SDK surface | What irtio answers with |
|---|---|---|
| Report the room a player is in, and whether others may join | updateRoom({ roomId, isJoinable, inviteParams }) | room.id, and your own seat count |
| An invite link a player can send a friend | inviteLink(params), read back with getInviteParam / inviteParams | the room code, the same one ?room= carries |
| Handle an accepted invite without a page reload | addJoinRoomListener(handler) | leave the room and join the new code |
| Launch straight into multiplayer when the portal says so | isInstantMultiplayer | matchRoom, quick-match |
| Signal loading and gameplay, as every published game must | loadingStart / loadingStop, gameplayStart / gameplayStop | nothing; they are portal telemetry |
The SDK itself is injected by the portal from https://sdk.crazygames.com/crazygames-sdk-v3.js.
The adapter never bundles it and never loads it: it reads window.CrazyGames when you call it, and
throws a CrazyGamesSdkError naming that script tag when it is not there. On your own dev server it
is not there, so guard with crazyGamesAvailable() if one build has to run in both places.
The boot path
crazyGamesEntry() collapses the portal’s intent into the one decision a multiplayer game makes at
boot: join a named room, find any room, or show a menu.
import { joinRoom, matchRoom } from '@irtio/client';
import { crazyGamesEntry, initCrazyGames, publishCrazyGamesInvite } from '@irtio/crazygames';
import { schema } from './schema';
await initCrazyGames(); // the SDK must finish init before any other call
const entry = crazyGamesEntry();
const room =
entry.kind === 'invite'
? await joinRoom(schema, { room: entry.room })
: entry.kind === 'instant'
? await matchRoom(schema, { queue: '1v1' })
: await joinRoom(schema); // your own front door: create by join
publishCrazyGamesInvite(room.id); An invite wins over the instant flag. A player who followed a friend’s link wants that friend’s room, not the next open one.
publishCrazyGamesInvite(room.id) is the portal-side counterpart of room.link. It reports the
room with updateRoom and returns the portal’s invite link. Use it instead of room.link here: a
link into your own origin is not something a player inside the portal’s page can open. Pass { inviteButton: true } if you also want the portal to draw its invite button.
Close the room when the match starts or the last seat goes, so the portal stops sending players into a game they cannot enter, and leave it when the player does:
closeCrazyGamesRoom(room.id); // reported, no longer joinable
leaveCrazyGamesRoom(); // the player is out Invites that arrive mid-game
A player can accept an invite while your game is already running. Handling it in place is one of the portal’s requirements, and it is a room swap:
const off = onCrazyGamesJoin(async (code) => {
await room.leave();
room = await joinRoom(schema, { room: code });
}); The room parameter
The room code travels on the invite link under the parameter room, matching the ?room= query
parameter joinRoom reads and publishRoomToLocation writes, so a code looks the same wherever it
came from. Every entry point takes { param: 'roomId' } if your game already ships another name.
Carry anything else you need beside it with { params: { region: 'eu' } }.
The origins step
Your build runs from games.crazygames.com, inside pages on the portal’s many domains. irtio
matches allowed origins on scheme, host and port, so the one origin to add is the iframe’s:
https://games.crazygames.com Add it on the dashboard under API keys. Without it the portal’s copy of your game cannot join anything, while your own site keeps working, which makes this the failure that looks like a bug in the adapter and is not. The portal’s full domain list, which matters for your bundle’s own sitelock rather than for irtio, is at docs.crazygames.com/resources/html5/sitelock.
Lifecycle calls
initCrazyGames, crazyGamesLoadingStart, crazyGamesLoadingStop, crazyGamesGameplayStart and crazyGamesGameplayStop are passthroughs, exported so a game that already imports this package
does not have to reach past it into window.CrazyGames. They are required of every published game,
multiplayer or not, and the portal decides what they mean. The rest of the SDK, ads and data and
user and banners included, stays yours to call directly.
Limits
- The adapter is glue over a browser SDK that only exists on the portal. Automated coverage mocks
window.CrazyGames, so the first real check of an integration is the portal’s own QA tool. - Room data is advisory. The portal decides who it sends where, and
isJoinable: falseis a request, not a fence. Keep your seat limits on the server, where a room config enforces them. - The portal’s friends list, usernames and the
disableChatsetting are onCrazyGames.SDK.userandCrazyGames.SDK.game.settings. Their requirements page asks multiplayer games to use them; this package does not wrap them, because none of them touches an irtio room. - Quick-match under
isInstantMultiplayeris your matchmaker’s job. If your queue can leave a player waiting, that wait is what the portal’s instant surface will show them, so size the queue for the traffic a portal front page sends.
Next: matchmaking for the queue behind instant multiplayer, and the Poki playbook for the other large portal, which takes docs rather than code.