Google Ad Placement API: adBreak() basics and policy do's and don'ts
Show interstitial and rewarded ads in your own HTML5 game with the Ad Placement API: setup, placement types, callbacks, test mode, and Google's placement rules.
The Ad Placement API is Google’s way to show interstitial and rewarded ads in an HTML5 game that you host yourself or distribute to publishers, paid through AdSense as part of the H5 Games Ads program. Unlike portal SDKs, you do not report gameplay events. You declare places where an ad could appear, and Google decides whether one does. This guide covers access, setup, the two functions, placement types, testing, and the placement rules, using the official documentation as of 2026-09-30.
Before you write code: access and approval
According to the sign-up page, H5 Games Ads is a by-application product: you apply through a form and approval depends on partner eligibility. You also need an approved AdSense account. On top of that, AdSense only serves ads on a site after it has passed review and shows a “Ready” status in your AdSense sites list (AdSense site management). The domain that hosts your game therefore has to be added and approved in AdSense like any other site.
The Google H5 Games Ads page on this site summarises the revenue share and payment threshold with links to the AdSense Help Center.
The tag and the two-line shim
The game page needs the AdSense tag and a small shim that defines adBreak and adConfig as functions that push to adsbygoogle. From the HTML5 game structure page:
<script async
data-ad-frequency-hint="30s"
src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?client=ca-pub-XXXXXXXXXXXXXXXX"
crossorigin="anonymous"></script>
<script>
window.adsbygoogle = window.adsbygoogle || [];
var adBreak = adConfig = function(o) {adsbygoogle.push(o);}
</script>
Google says the tag and every API call should run in the same document as the game canvas. If the game is embedded in an iframe on another page, the tag goes inside the iframe, and Google recommends adding allow="autoplay" to that iframe element.
data-ad-frequency-hint is a hint for how often interstitials may show; the documentation’s examples use 30s.
adConfig(): tell the API about your game
adConfig() takes three optional parameters:
sound: 'on' | 'off'says whether the game currently plays sound, so the API can pick suitable ads. Call it again when the player mutes or unmutes.preloadAdBreaks: 'on' | 'auto':'on'forces ads to preload before the firstadBreak(); the default,'auto', leaves the decision to the API. The preload page notes that once set, later attempts to change it are ignored, so set it once, early.onReadyis a callback for when the API has initialised and finished preloading.
adBreak(): placements and callbacks
adBreak() takes a placement config. type is required; Google recommends always giving a name as well, an internal identifier it says may be used for reporting in future releases. The placement types describe the game’s state:
preroll: before the game’s UI loads and before any sound. Only one per page load.start: the game has loaded and the UI is visible, but play has not started.pause: the player paused the game.next: the player is moving to the next level.browse: the player is browsing options outside gameplay.reward: an opt-in rewarded ad.
The callbacks:
beforeAd(): pause the game and mute its sound. Only called if an ad will show.afterAd(): resume and unmute.beforeReward(showAdFn): only forrewardplacements, and only called when a rewarded ad is available. Show your reward prompt and callshowAdFn()if the player accepts.adViewed()andadDismissed(): the player watched the rewarded ad to the end, or closed it early. Grant the reward only inadViewed().adBreakDone(placementInfo): always the last callback, whether or not an ad showed.
placementInfo.breakStatus says what happened. The reference lists notReady, timeout, invalid, error, noAdPreloaded, frequencyCapped, ignored (the player never accepted a reward prompt before the next placement), other, dismissed and viewed. Log it while you integrate; it explains most “no ad showed” questions.
Rules for rewards
The API overview sets limits on what a reward can be: it must not have value outside your game, must not have or be easily exchanged for monetary value, must not be saleable or exchangeable for goods and services, and you must not encourage players to click ads.
Where full-screen ads are not allowed
The H5 Games Ads guidance in the AdSense Help Center allows full-screen ads at natural transition points, such as between levels, and lists placements that are not allowed. Paraphrased, full-screen ads must not:
- appear before the game or page has opened, where a player might think clicking the ad is part of the startup;
- appear after the game has exited or the page closed;
- follow directly after the player closed another full-screen ad;
- appear unexpectedly while the player is viewing content;
- trigger after every interaction;
- interrupt continuous gameplay or heavy interaction;
- interfere with navigation.
In practice: request next placements when a level ends and the player chooses to continue, pause when they pause, and let Google’s frequency controls decide the rest. Do not call adBreak() from inside your game loop.
The adapter
The site’s example game uses the same adapter interface for every portal. For the Ad Placement API, commercialBreak() becomes a next placement (or start when the game restarts after a game over, which the placement-types page allows) and rewardedBreak() a reward placement that only shows your prompt when Google calls beforeReward:
// file: examples/lib/adapters/adplacement.js
// Google Ad Placement API (H5 Games Ads) adapter.
// Docs: https://developers.google.com/ad-placement/apis , /apis/adbreak , /apis/adconfig ,
// /docs/placement-types , /docs/test (checked 2026-09-30)
// index.html must contain the adsbygoogle.js tag with your ca-pub ID and the documented shim:
// window.adsbygoogle = window.adsbygoogle || [];
// var adBreak = adConfig = function(o) {adsbygoogle.push(o);}
import { gameplayGuard } from './types.js';
import { createNoopAdapter } from './noop.js';
/**
* @param {{ adBreak?: (o: object) => void, adConfig?: (o: object) => void }} [api] injectable for tests
* @returns {Promise<import('./types.js').PortalAdapter>}
*/
export async function createAdPlacementAdapter(api = /** @type {any} */ (globalThis)) {
const { adBreak, adConfig } = api;
if (typeof adBreak !== 'function' || typeof adConfig !== 'function') {
return createNoopAdapter('Ad Placement API shim missing from index.html');
}
// The shim only queues calls: if adsbygoogle.js is blocked or fails to load, nothing ever calls
// back. The API calls onReady once it has loaded and initialised, so placements wait for that.
let ready = false;
// Preload so the first placement has an ad ready; tell the API the game has sound.
adConfig({
preloadAdBreaks: 'on',
sound: 'on',
onReady: () => {
ready = true;
},
});
// The API has no gameplay or loading events; keep the guard so game code stays identical.
const guard = gameplayGuard(
() => {},
() => {},
);
return {
name: 'google-ad-placement',
loadingStart() {},
loadingStop() {},
...guard,
commercialBreak(hooks) {
guard.gameplayStop();
// Not ready: skip this placement instead of queueing it (it would never resolve, or show later at
// a random moment once a slow loader arrives).
if (!ready) return Promise.resolve();
return new Promise((resolve) => {
adBreak({
type: hooks.placement ?? 'next', // 'next': the player finished a level and moves on
name: hooks.name,
beforeAd: () => hooks.pause(),
afterAd: () => hooks.resume(),
// Always called last, whether or not an ad showed.
adBreakDone: () => resolve(undefined),
});
});
},
rewardedBreak(hooks) {
guard.gameplayStop();
if (!ready) return Promise.resolve(false); // never offer a reward the API cannot deliver
return new Promise((resolve) => {
let settled = false;
let rewarded = false;
const finish = (/** @type {boolean} */ value) => {
if (!settled) {
settled = true;
resolve(value);
}
};
adBreak({
type: 'reward',
name: hooks.name,
beforeAd: () => hooks.pause(),
afterAd: () => hooks.resume(),
// Only called when a rewarded ad is available: show the prompt then.
beforeReward: (/** @type {() => void} */ showAdFn) =>
hooks.showPrompt(
() => showAdFn(),
() => finish(false),
),
adViewed: () => {
rewarded = true;
},
adDismissed: () => {
rewarded = false;
},
adBreakDone: () => finish(rewarded),
});
});
},
onMuteRequest() {},
};
}
/**
* Report the game's sound state so the API can pick suitable ads.
* @param {{ adConfig?: (o: object) => void }} api
* @param {boolean} soundOn
*/
export function reportSound(api, soundOn) {
api.adConfig?.({ sound: soundOn ? 'on' : 'off' });
}
Three details are easy to miss. First, the inline shim only queues calls into an array. If adsbygoogle.js is blocked or fails to load, nothing ever calls back, and a game that awaits adBreakDone would wait forever. The adapter therefore waits for the onReady callback of adConfig(), which the API calls once it has initialised, and skips placements until then instead of queueing them (a queued placement could otherwise show much later, at a random moment, once a slow loader arrives). The trade-off: breaks requested before the API is ready show no ad. Second, the promise for a rewarded break resolves false immediately when the player declines, because the ignored status only arrives at the next placement. Third, the game never shows a reward button on its own: if no rewarded ad is available, beforeReward is never called and adBreakDone reports why.
The full index.html for the vanilla example, including the test attribute:
<!-- file: examples/vanilla-adplacement/index.html -->
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Tap game: Google Ad Placement API example (vanilla JS)</title>
<!-- Google Ad Placement API (developers.google.com/ad-placement/docs/html5-game-structure).
Replace ca-pub-XXXXXXXXXXXXXXXX with your AdSense publisher ID.
data-adbreak-test="on" shows mock ads (developers.google.com/ad-placement/docs/test): remove it before release. -->
<script async data-adbreak-test="on" data-ad-frequency-hint="30s"
src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?client=ca-pub-XXXXXXXXXXXXXXXX"
crossorigin="anonymous"></script>
<script>
window.adsbygoogle = window.adsbygoogle || [];
var adBreak = adConfig = function(o) {adsbygoogle.push(o);}
</script>
<style>
html, body { margin: 0; height: 100%; background: #191c3a; overflow: hidden; }
canvas { display: block; width: 100vw; height: 100vh; touch-action: none; }
#overlay { position: fixed; inset: 0; display: grid; place-content: center; gap: 12px; text-align: center;
color: #e6e8f5; font: 20px system-ui, sans-serif; background: rgba(25, 28, 58, 0.85); }
#overlay[hidden] { display: none; }
#overlay button { font: inherit; padding: 10px 18px; }
</style>
</head>
<body>
<canvas id="game"></canvas>
<div id="overlay" hidden></div>
<script type="module" src="main.js"></script>
</body>
</html>
Test mode
Add data-adbreak-test="on" to the adsbygoogle.js tag, as in the example above. According to the testing page, testing mode shows mock ads instead of real ones, respects your frequency settings, and alternates between “ad loaded” and “ad not loaded” so you exercise both paths. It does not send ad requests to Google, so it cannot catch a wrong data-ad-client value. Remove the attribute before release: the Portal Readiness Checker reports it if it is still in your build.
What to check before you submit
- Your AdSense account is approved, you have H5 Games Ads access, and the hosting domain shows “Ready” in AdSense.
- The tag, the shim and every
adBreak()call live in the same document as the game canvas; an embedding iframe hasallow="autoplay". adConfig({ preloadAdBreaks: 'on' })runs once before the first placement;soundis updated when the player mutes.- Every placement has a correct
typeand (recommended) aname. beforeAdpauses and mutes,afterAdresumes, and the game continues fromadBreakDoneeven when no ad showed.- The game still plays normally when
adsbygoogle.jscannot load (test with an ad blocker): no break waits for a callback that never comes. - Rewards are granted only in
adViewed(), and only offered frombeforeReward. - No placements during active play, after every interaction, or directly after another full-screen ad.
data-adbreak-testis removed andca-pub-XXXXXXXXXXXXXXXXis replaced with your publisher ID.
Limits of this guide
This guide covers web games on your own domain. It does not cover H5 Games Ads inside Android WebViews, which Google routes through AdMob, advanced reporting, or the preroll flow in depth. The adapter has been unit-tested against a mock of adBreak() and adConfig() that follows the documented callback order; it has not served real ads, because that requires an approved account.
Questions
- Can I use the Ad Placement API on any website?
- Only after Google approves you. H5 Games Ads is a by-application product, you need an approved AdSense account, and ads only serve on sites that have passed AdSense review and show a Ready status.
- Does every adBreak() call show an ad?
- No. adBreak() declares a place where an ad could show. Google decides based on the placement type, ad availability, frequency settings and the player's consent. Your adBreakDone callback reports what happened through placementInfo.breakStatus.
- How do I test without real ads?
- Add data-adbreak-test="on" to the adsbygoogle.js script tag. Google's testing mode shows mock ads and alternates between an ad loading and not loading. Remove the attribute before release.
Sources
- Ad Placement API: How to sign up
- Ad Placement API: Structure of an HTML5 game
- Ad Placement API: API overview
- Ad Placement API reference: adBreak()
- Ad Placement API reference: adConfig()
- Ad Placement API: Placement types
- Ad Placement API: Testing modes
- Ad Placement API: Preload ads
- AdSense Help: Get started with AdSense H5 Games Ads
- AdSense Help: AdSense site management