GameDistribution SDK: GD_OPTIONS, showAd() and rewarded ads

Integrate the GameDistribution HTML5 SDK: load it once, pause and mute on SDK_GAME_PAUSE, pre-roll on Play, mid-rolls, and rewarded ads that pay out only on completion.

Published
Sources checked
Versions
GameDistribution HTML5 SDK (main.min.js), docs as of 2026-10-01, Phaser 3.90.0

GameDistribution distributes HTML5 games to a network of publisher sites and pays a share of ad revenue (see the GameDistribution page for the terms). Its SDK is event-driven: you hand it a configuration object with one callback, and it tells your game when to pause and when to resume. This guide integrates it into the same small game used across this site’s SDK guides, with code that is bundled and unit-tested in the site’s repository. Documentation checked on 2026-10-01.

Load the SDK once, while the game loads

The SDK page gives a snippet that sets window["GD_OPTIONS"] and then injects https://html5.api.gamedistribution.com/main.min.js. Two placement rules come with it:

  • Load the SDK before or while your game loads, not later and never from a button press, so an ad is ready when the player clicks Play.
  • Load it only once.

GD_OPTIONS takes your gameId (the game hash from your GameDistribution control panel), an onEvent callback, an optional prefix for ID conflicts, and advertisementSettings. The page notes the object is safe to obfuscate.

The events your game must handle

onEvent receives objects with a name. The mandatory settings are about two of them:

  • SDK_GAME_PAUSE: an ad is about to play. Pause and mute the game; GameDistribution says background audio during video ads is forbidden.
  • SDK_GAME_START: the ad is over. Resume the game.

Others you may see: SDK_READY, SDK_ERROR, SDK_GDPR_TRACKING, SDK_GDPR_TARGETING, SDK_GDPR_THIRD_PARTY and, for rewarded ads, SDK_REWARDED_WATCH_COMPLETE. The GDPR events matter only if you use other tracking or ad services; GameDistribution’s own ads are handled by the SDK.

Because pausing is driven by events rather than by the call you made, your pause and resume functions must be safe to call at any time, including when no ad shows at all.

Pre-roll and mid-rolls

The developer guidelines make pre-roll and mid-roll ads mandatory for all games; rewarded and display ads are optional. Both mandatory types use the same call, gdsdk.showAd():

  • Pre-roll: typically after a click on Play. The SDK page suggests Start, Play or Continue buttons on the splash screen.
  • Mid-rolls: at natural breaks, ideally on non-gameplay buttons such as Replay, Next or Menu on the game-over or win screen.

The rules that come with them: ads only after user input (a click or tap), only outside gameplay, game muted and paused while the ad shows. You do not need your own frequency limit; the SDK page says the SDK regulates the interval and you can call showAd() as often as you like. The guidelines add that when a time-based mid-roll ends, players should land on a pause screen and resume only when they choose to.

GameDistribution also asks you to check that gdsdk and gdsdk.showAd exist before each call, because an ad blocker or an outage can stop the script from loading. The wiki’s rewarded-ad example chains .then() and .catch() on showAd(), so it returns a promise; the adapter below awaits it for interstitials too and treats a rejection as “no ad”.

Rewarded ads

The Rewarded Ads wiki page recommends preloading first, then showing the ad when the player asks for the reward:

  1. gdsdk.preloadAd('rewarded') resolves when a rewarded ad can be shown and rejects when none is available. Only show your “watch an ad” button after it resolves.
  2. When the player accepts, call gdsdk.showAd('rewarded').
  3. Give the reward only if SDK_REWARDED_WATCH_COMPLETE fired. If the promise rejects, give nothing.

The wiki also warns that rewarded ads must be switched on for your game in the GameDistribution developer panel, or the request fails. The guidelines repeat the core rule: a reward only after a completed impression.

The adapter

The example game talks to a small adapter instead of the SDK. This one installs GD_OPTIONS, loads the script once, maps the two pause events onto the game’s pause and resume functions, and resumes the game itself if an ad fails before SDK_GAME_START arrives. It sets breakOnStart, which tells the game to request a break when the player first presses Play: that is the pre-roll.

// file: examples/lib/adapters/gamedistribution.js
// GameDistribution HTML5 SDK adapter.
// Docs: https://gamedistribution.com/developers/sdk/html5/ and the official wiki
//       https://github.com/GameDistribution/GD-HTML5/wiki (Rewarded Ads) (checked 2026-10-01)
// The SDK is configured through window.GD_OPTIONS and reports everything through onEvent:
// SDK_GAME_PAUSE (pause and mute), SDK_GAME_START (resume), SDK_REWARDED_WATCH_COMPLETE, SDK_READY.
import { gameplayGuard } from './types.js';

const SCRIPT_ID = 'gamedistribution-jssdk';
const SCRIPT_SRC = 'https://html5.api.gamedistribution.com/main.min.js';

/**
 * Installs GD_OPTIONS and loads the SDK once (the documented snippet, as a function).
 * Call it at startup, not from a button: GameDistribution asks for the SDK to load while the game loads.
 * @param {{ gameId: string, win?: any, doc?: any, loadScript?: boolean }} options
 * @returns {import('./types.js').PortalAdapter}
 */
export function createGameDistributionAdapter({ gameId, win = globalThis, doc = globalThis.document, loadScript = true }) {
  /** @type {import('./types.js').BreakHooks | null} */
  let active = null;
  let pausedByAd = false;
  let rewardEarned = false;

  win.GD_OPTIONS = {
    gameId,
    onEvent(/** @type {{ name: string }} */ event) {
      switch (event.name) {
        case 'SDK_GAME_PAUSE': // an ad is about to play: pause AND mute
          if (active && !pausedByAd) {
            pausedByAd = true;
            active.pause();
          }
          break;
        case 'SDK_GAME_START': // the ad is over: resume and unmute
          if (active && pausedByAd) {
            pausedByAd = false;
            active.resume();
          }
          break;
        case 'SDK_REWARDED_WATCH_COMPLETE':
          rewardEarned = true;
          break;
      }
    },
  };

  if (loadScript && doc && !doc.getElementById(SCRIPT_ID)) {
    const first = doc.getElementsByTagName('script')[0];
    const js = doc.createElement('script');
    js.id = SCRIPT_ID;
    js.src = SCRIPT_SRC;
    first.parentNode.insertBefore(js, first);
  }

  // GameDistribution's docs: check that gdsdk and the method exist before each call (ad blockers, outages).
  const sdk = () => (win.gdsdk && typeof win.gdsdk.showAd === 'function' ? win.gdsdk : null);

  /** Runs one ad and makes sure the game is resumed even if SDK_GAME_START never arrives. */
  async function runAd(/** @type {import('./types.js').BreakHooks} */ hooks, /** @type {string | undefined} */ type) {
    const gd = sdk();
    if (!gd) return false;
    active = hooks;
    pausedByAd = false;
    let ok = true;
    try {
      await (type ? gd.showAd(type) : gd.showAd());
    } catch {
      ok = false; // no fill, ad blocker, or an error: the game simply continues
    } finally {
      if (pausedByAd) {
        pausedByAd = false;
        hooks.resume();
      }
      active = null;
    }
    return ok;
  }

  // No gameplay or loading events in this SDK; the guard keeps game code identical across portals.
  const guard = gameplayGuard(
    () => {},
    () => {},
  );

  return {
    name: 'gamedistribution',
    breakOnStart: true, // GameDistribution asks for a pre-roll on Start/Play buttons
    loadingStart() {},
    loadingStop() {},
    ...guard,
    async commercialBreak(hooks) {
      guard.gameplayStop();
      await runAd(hooks, undefined); // interstitial (pre-roll or mid-roll)
    },
    async rewardedBreak(hooks) {
      const gd = sdk();
      if (!gd || typeof gd.preloadAd !== 'function') return false;
      try {
        await gd.preloadAd('rewarded'); // rejects when no rewarded ad is available: do not offer one
      } catch {
        return false;
      }
      return new Promise((resolve) => {
        hooks.showPrompt(
          async () => {
            guard.gameplayStop();
            rewardEarned = false;
            const ok = await runAd(hooks, 'rewarded');
            // Reward only when the SDK reported a complete view; never on errors.
            resolve(ok && rewardEarned);
          },
          () => resolve(false),
        );
      });
    },
    // The SDK has no portal-level mute setting; ads pause and mute through SDK_GAME_PAUSE.
    onMuteRequest() {},
  };
}

gameplayGuard is a small helper shared by all adapters; GameDistribution has no gameplay events, so here it only keeps the game code identical across portals.

The vanilla entry point:

// file: examples/vanilla-gamedistribution/main.js
// GameDistribution SDK example, vanilla JS. Build: npm run examples:build, then serve examples/dist/ on localhost.
import { createGameDistributionAdapter } from '../lib/adapters/gamedistribution.js';
import { startTapGame } from '../lib/tap-game.js';

const adapter = createGameDistributionAdapter({ gameId: 'YOUR_GD_GAME_ID' });
adapter.loadingStart();
// Load your assets here. This example has none, so loading ends immediately.
adapter.loadingStop();
startTapGame({
  canvas: /** @type {HTMLCanvasElement} */ (document.getElementById('game')),
  overlay: /** @type {HTMLElement} */ (document.getElementById('overlay')),
  adapter,
});
console.info(`[example] portal adapter: ${adapter.name}`);

The game’s Play button now calls adapter.commercialBreak() before the first level when breakOnStart is set, and its level-complete and game-over buttons request mid-rolls. All three are button presses outside gameplay, which is what the guidelines ask for.

Phaser 3

The Phaser 3.90.0 version uses the same adapter and the same scenes as the other portal examples; only the entry point changes:

// file: examples/phaser-gamedistribution/main.js
// GameDistribution SDK example, Phaser 3. Build: npm run examples:build, then serve examples/dist/ on localhost.
import { createGameDistributionAdapter } from '../lib/adapters/gamedistribution.js';
import { createPhaserGame } from '../lib/phaser-game.js';

const adapter = createGameDistributionAdapter({ gameId: 'YOUR_GD_GAME_ID' });
createPhaserGame(adapter, 'game');
console.info(`[example] portal adapter: ${adapter.name}`);

During an ad the Phaser example calls game.pause() and sets game.sound.mute, so both the loop and every sound stop until SDK_GAME_START.

Activating the integration

According to the SDK page, you activate the SDK by uploading the game in the GameDistribution developer admin, opening it in the provided iframe view, and watching the test pre-roll completely once. That flags the integration as valid so you can request publishing; the page says this can take up to two weeks. While debugging, gdsdk.openConsole() in the browser console opens a toolbar for test ads; clearing localStorage turns it off again.

Before uploading, run the Portal Readiness Checker on your zip. It recognises the GD_OPTIONS integration, reports whether showAd() and the two pause events appear in your code, and warns if another portal’s SDK is still in the build.

What to check before you submit

  • GD_OPTIONS has your real gameId, and the SDK script is injected once, at startup.
  • SDK_GAME_PAUSE pauses the game loop and mutes all audio; SDK_GAME_START resumes.
  • The game also continues when showAd() rejects or gdsdk is missing (test with an ad blocker).
  • A pre-roll is requested from the Play button, mid-rolls from non-gameplay buttons, never during play.
  • Rewarded buttons appear only after preloadAd('rewarded') resolves, rewards only after SDK_REWARDED_WATCH_COMPLETE, and rewarded ads are enabled in the developer panel.
  • No other portal’s SDK or ad code remains in the build.

Limits of this guide

This guide covers interstitial and rewarded video ads. It does not cover display (banner) ads, the store API, self-hosted games (the README describes an iframe setup with a GD_SDK_REFERRER_URL parameter, but the FAQ says externally hosted games are not accepted except real multiplayer games) or engine plugins. The adapter was unit-tested against a mock that fires the documented events; it has not been run against GameDistribution’s live ad server, which requires an activated game.

Questions

Where should the GameDistribution pre-roll go?
GameDistribution's guidelines describe the pre-roll as typically shown after a click on the Play button, and its SDK page recommends Start, Play or Continue buttons on the loading or splash screen.
How often can I call gdsdk.showAd()?
As often as there is a non-gameplay button press. GameDistribution says the SDK regulates the interval itself, so a call that comes too soon simply does not show an ad.
When do I give the reward for a rewarded ad?
Only when the SDK fires SDK_REWARDED_WATCH_COMPLETE, meaning the player watched the whole ad. The wiki says not to give a reward when showAd('rewarded') rejects.

Sources

  1. GameDistribution: Connect your game with our SDKs (HTML5)gamedistribution.com, accessed
  2. GameDistribution GD-HTML5 wiki: Rewarded Adsgithub.com, accessed
  3. GameDistribution GD-HTML5 repository (README)github.com, accessed
  4. GameDistribution: Developer guidelines (advertising section)static.gamedistribution.com, accessed