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 forIts SDK surfaceWhat irtio answers with
Report the room a player is in, and whether others may joinupdateRoom({ roomId, isJoinable, inviteParams })room.id, and your own seat count
An invite link a player can send a friendinviteLink(params), read back with getInviteParam / inviteParamsthe room code, the same one ?room= carries
Handle an accepted invite without a page reloadaddJoinRoomListener(handler)leave the room and join the new code
Launch straight into multiplayer when the portal says soisInstantMultiplayermatchRoom, quick-match
Signal loading and gameplay, as every published game mustloadingStart / loadingStop, gameplayStart / gameplayStopnothing; 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: false is 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 disableChat setting are on CrazyGames.SDK.user and CrazyGames.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 isInstantMultiplayer is 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.