CrazyGames SDK v3 in a Phaser 3 game: init, events and ads
Integrate the CrazyGames HTML5 SDK v3 into a Phaser 3 game: initialisation, loading and gameplay events, midgame and rewarded ads, muting, and local testing.
This guide wires the CrazyGames HTML5 SDK v3 into a Phaser 3 game. The code is the tap-game example from this site’s repository: a Phaser 3.90.0 game that talks to a small adapter instead of calling the SDK directly. The adapter is bundled with esbuild and unit-tested against a mocked SDK, so the snippets below are the code that actually builds.
Everything here comes from the CrazyGames SDK documentation as of 2026-09-30. CrazyGames revises its SDK and requirements, so check the linked pages before you submit.
Load and initialise the SDK
Add the script tag to the <head> of your index.html, before your game bundle:
<script src="https://sdk.crazygames.com/crazygames-sdk-v3.js"></script>
Version 3 must be initialised before any other call, and initialisation is asynchronous. The SDK introduction asks you to await window.CrazyGames.SDK.init(), ideally while your loading screen is up.
After init, read window.CrazyGames.SDK.environment. The docs describe three values:
localonlocalhostand127.0.0.1: ads are replaced by an overlay text and other events are logged to the console;crazygameson CrazyGames domains, where everything works;disabledon every other domain, including your own site. In this environment every SDK call throws, so guard your calls.
The adapter below returns a no-op implementation in the disabled case, so the same build still runs on your own domain.
Report loading and gameplay
The game module has two pairs of events.
loadingStart() and loadingStop() mark your loading phase. CrazyGames lists them as optional for a full integration, but they feed the loading-time reports on their side.
gameplayStart() and gameplayStop() are required for a Full Launch. Call gameplayStart() whenever the player starts or resumes playing: game start, unpause, revive, next level. Call gameplayStop() at every break: menus, level end, pause. Do not call it when the tab loses focus; CrazyGames handles that. The first gameplayStart() matters more than it looks: according to the technical requirements, the initial download size is measured from the start of loading to that first event, and it is compared with the published first-load limit shown on the CrazyGames portal page.
In Phaser, the natural places are a boot scene’s preload() for loadingStart(), its create() for loadingStop() (Phaser calls create() after the loader finishes), and the start and end of your play scene for the gameplay pair. The adapter ignores repeated gameplayStart() calls while already playing, which keeps the event stream clean if two code paths resume the game.
Midgame and rewarded ads
The video ads module has one call, requestAd(type, callbacks), with type set to "midgame" or "rewarded", and three callbacks: adStarted, adFinished and adError.
The advertisement requirements add rules your code has to respect:
- Show midgame ads only at natural breaks such as a level transition or after the player dies, never while they are playing and not on navigation buttons like the main menu or settings.
- Block input from the moment you request an ad until
adFinishedoradErrorfires. A request takes time. - Mute and pause only when the ad actually starts (
adStarted), not when you request it. A request may return no ad, and muting without anything visible happening is confusing. - Handle
adErroras “continue the game”. CrazyGames lists these error codes:adsDisabledBasicLaunch,unfilled,adblock,adCooldownandother. - You do not need your own frequency cap. The docs say midgame ads are limited to at most one every 3 minutes and early requests are simply ignored.
- Only ads requested through the CrazyGames SDK are allowed.
For rewarded ads, give the reward in adFinished. Make rewards a bonus rather than a requirement: the requirements page rejects level designs that can only be completed by watching an ad.
Respect the portal’s mute setting
window.CrazyGames.SDK.game.settings.muteAudio tells you CrazyGames wants the game silent, and addSettingsChangeListener() reports changes. The game module docs say this setting takes priority over your own sound toggle. A full HTML5 implementation requires muteAudio support. You can force it locally with ?muteAudio=true in the URL.
The complete adapter
This is the whole CrazyGames adapter used by both the vanilla and the Phaser example. It exposes the same interface as the Poki and Ad Placement adapters, so the game code does not change between portal builds.
// file: examples/lib/adapters/crazygames.js
// CrazyGames HTML5 SDK v3 adapter.
// Docs: https://docs.crazygames.com/sdk/intro/ , https://docs.crazygames.com/sdk/game/ ,
// https://docs.crazygames.com/sdk/video-ads/ (checked 2026-09-30)
// index.html must load https://sdk.crazygames.com/crazygames-sdk-v3.js before the game code.
import { gameplayGuard } from './types.js';
import { createNoopAdapter } from './noop.js';
/**
* @param {any} [sdk] window.CrazyGames.SDK (injectable for tests)
* @returns {Promise<import('./types.js').PortalAdapter>}
*/
export async function createCrazyGamesAdapter(sdk = globalThis.CrazyGames?.SDK) {
if (!sdk) return createNoopAdapter('CrazyGames SDK script not loaded');
// v3 must be initialised before any other call; init is asynchronous.
await sdk.init();
// Outside CrazyGames domains and localhost the SDK is "disabled" and every call throws.
if (sdk.environment === 'disabled') return createNoopAdapter('CrazyGames SDK disabled on this domain');
const guard = gameplayGuard(
() => sdk.game.gameplayStart(),
() => sdk.game.gameplayStop(),
);
/** @param {'midgame' | 'rewarded'} type @param {import('./types.js').BreakHooks} hooks */
const requestAd = (type, hooks) =>
new Promise((resolve) => {
let paused = false;
const end = (/** @type {boolean} */ shown) => {
if (paused) hooks.resume();
resolve(shown);
};
sdk.ad.requestAd(type, {
// Pause and mute only when the ad actually starts.
adStarted: () => {
paused = true;
hooks.pause();
},
adFinished: () => end(true),
// Also fires for unfilled ads, adblock, cooldown and Basic Launch: the game must continue.
adError: () => end(false),
});
});
return {
name: 'crazygames',
loadingStart: () => sdk.game.loadingStart(),
loadingStop: () => sdk.game.loadingStop(),
...guard,
async commercialBreak(hooks) {
guard.gameplayStop();
await requestAd('midgame', hooks);
},
rewardedBreak(hooks) {
return new Promise((resolve) => {
hooks.showPrompt(
async () => {
guard.gameplayStop();
resolve(await requestAd('rewarded', hooks));
},
() => resolve(false),
);
});
},
onMuteRequest(listener) {
// settings.muteAudio overrides the game's own audio toggle.
listener(Boolean(sdk.game.settings?.muteAudio));
sdk.game.addSettingsChangeListener((/** @type {{ muteAudio?: boolean }} */ s) => listener(Boolean(s.muteAudio)));
},
};
}
Pausing a Phaser 3 game during an ad
The adapter calls hooks.pause() from adStarted and hooks.resume() when the ad ends. For a Phaser game, pausing means stopping the game step and silencing every sound. Phaser 3.90 provides Game#pause() and Game#resume() for the loop and a global mute flag on the sound manager:
// From examples/lib/phaser-game.js
const hooks = (game) => ({
pause() {
game.sound.mute = true;
game.pause();
},
resume() {
game.resume();
game.sound.mute = portalMuted; // keep CrazyGames' muteAudio setting in force
},
});
The level-complete and game-over screens ask for a break before gameplay resumes:
button.once('pointerdown', async () => {
if (data.breakFirst) {
const restart = data.level === 1;
const name = restart ? 'restart-after-game-over' : `level-${data.level - 1}-complete`;
await adapter.commercialBreak({ name, placement: restart ? 'start' : 'next', ...hooks(this.game) });
}
this.scene.start('play', { level: data.level });
});
Because the button uses once, a second tap during the ad request cannot start the level twice, which covers the “block input while requesting” rule.
The entry point is three lines:
// file: examples/phaser-crazygames/main.js
// CrazyGames SDK v3 example, Phaser 3. Build: npm run examples:build, then serve examples/dist/ on localhost.
import { createCrazyGamesAdapter } from '../lib/adapters/crazygames.js';
import { createPhaserGame } from '../lib/phaser-game.js';
createCrazyGamesAdapter().then((adapter) => {
createPhaserGame(adapter, 'game');
console.info(`[example] portal adapter: ${adapter.name}`);
});
Test locally, then in the Preview tool
- Build the example with
npm run examples:buildand serveexamples/dist/onlocalhost. The SDK reports thelocalenvironment, shows an overlay instead of real ads, and logs gameplay events to the console. If you develop on another host name, the docs say you can append?useLocalSdk=trueto force the local environment. - Run the Portal Readiness Checker on your zipped build. It lists which of
init,gameplayStart,gameplayStop,loadingStart,loadingStopandrequestAdappear in your code, and compares the build with CrazyGames’ published size limits. - Upload the build in the CrazyGames Developer Portal and open it in the Preview tool, which the SDK introduction describes as the most realistic version of the site.
What to check before you submit
- The SDK script loads before your game code, and
init()is awaited before any other SDK call. - No SDK calls run when
environmentisdisabled. gameplayStart()fires when play begins or resumes,gameplayStop()at every break, and the firstgameplayStart()comes as early as your game allows (it ends the initial-download measurement).- Midgame ads are requested only at natural breaks, never from menu or settings buttons.
- Audio is muted and the game paused on
adStarted, and both are restored onadFinishedandadError. - The game continues normally when an ad errors (test with an ad blocker on).
muteAudiooverrides your own sound toggle.- No other ad network code or other portal SDK is in the build.
Limits of this guide
This guide covers the HTML5 SDK’s game and video-ad modules. It does not cover banners, user accounts, the data module, in-game purchases or multiplayer invites; see the SDK introduction for those. We have not tested the adapter on the live CrazyGames site; the unit tests only check that it calls the SDK in the order the documentation describes.
Questions
- Do I need the SDK for a Basic Launch on CrazyGames?
- No. CrazyGames describes the SDK as optional for Basic Launch and mandatory for Full Launch. If you do integrate it for Basic Launch, a gameplay start event is used to measure your initial download size, and ads stay disabled until Full Launch.
- Why does my game crash when I host it on my own domain?
- Outside CrazyGames domains and localhost the SDK runs in the 'disabled' environment, where every SDK call throws. Check window.CrazyGames.SDK.environment after init and skip SDK calls when it is 'disabled'.
- How often can I request a midgame ad?
- As often as there is a natural break. CrazyGames enforces the interval itself (its docs mention at most one midgame ad every 3 minutes) and ignores requests that come too early, returning an adCooldown error.