Game-only embeds: implementation and handoff
Updated: 2026-09-18. Compatible with SDK runtime v1.4.0. This is a layout and delivery guide, not a new SDK protocol version.
SDK integration and embed readiness are separate
The SDK connects identity, scores, saves and optional platform features. Loading it does not remove a website header, create /embed, resize a canvas, translate game content or remove scrollbars. The game team implements the embedded presentation; tokenApps loads the registered player URL.
If a full homepage is registered, its hero, navigation, rankings, footer and FAQ appear inside the iframe too. Making the host wider cannot remove those sections. Cross-origin rules prevent tokenApps from rewriting the game's DOM or CSS.
| Responsibility | Game project | tokenApps |
|---|---|---|
| Game-only route and responsive game | Implement and deploy | Load the supplied URL |
| Board, HUD, touch controls and game dialogs | Fit inside the frame | Provide available frame space |
| Identity, scores and saves | Call the existing SDK | Authenticate and process requests |
| Farming | Keep play usable; no local TAP payout | Measure eligible play and settle rewards |
| Review | Supply evidence from supported devices | Review the build and listing |
1. Deliver a real game-only URL
Recommended structure:
https://game.example.com/ Public website: introduction, SEO, FAQ
https://game.example.com/embed Game board + HUD + essential controls
These are illustrative addresses. /embed must be implemented and deployed; adding the path or ?embed=1 to an existing URL does nothing unless the game handles it. A different path is fine. If / already serves only the game, keep it.
Reuse the existing game component, engine and save format. Do not create a second copy of the game logic. Keep pause, sound, help, restart, score, remaining lives/coins and touch actions available. Open secondary help or rankings in a bounded dialog when needed instead of adding long sections below the board.
Do not place a second iframe around the game: the SDK communicates with its immediate parent. Load the SDK in the document that tokenApps directly embeds.
2. Separate the website layout
For React/Vite, route /embed directly to the shared game component without the marketing layout. Configure the deployed server's SPA fallback so direct navigation and refresh on /embed work. For plain HTML, an embed.html document using the same game module is sufficient.
For Next.js App Router, keep the root layout minimal and move website chrome into a route group:
app/layout.tsx html, body, shared providers; no site header/footer
app/(website)/layout.tsx Website header, navigation, footer
app/(website)/page.tsx Public homepage at /
app/embed/page.tsx Shared game client component
components/Game.tsx Existing game engine and controls
Route groups do not add a URL segment. Do not leave another app/page.tsx competing for /. Put browser-only engine initialization in a client effect, never during server rendering. Clean up the engine, observers, input listeners and animation frames on unmount. React development Strict Mode may run setup/cleanup again; avoid duplicate canvases and game loops.
Scope embed CSS to its route or root. Do not globally hide scrolling on the public website. See the Next.js route group reference.
3. Allocate space before scaling the board
The iframe's viewport is the available size. It is smaller than the browser window because tokenApps has its own header and controls; its size changes on rotation and fullscreen. Do not subtract tokenApps header heights inside the game, use screen.width, or assume a fixed 900px height.
Start with a game-only document containing three rows:
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover">
<main id="embed-root">
<header class="game-hud">Score, lives and pause</header>
<div id="board-space"><canvas id="board"></canvas></div>
<nav class="game-controls" aria-label="Game controls">Touch buttons</nav>
</main>
/* Only in the game-only document; every framework wrapper needs a height. */
html, body, #root { width: 100%; height: 100%; margin: 0; }
body { overflow: hidden; overscroll-behavior: none; }
#embed-root {
position: fixed; inset: 0; box-sizing: border-box;
display: grid; grid-template-rows: auto minmax(0, 1fr) auto;
gap: 8px; min-width: 0; min-height: 0;
padding: max(8px, env(safe-area-inset-top))
max(8px, env(safe-area-inset-right))
max(8px, env(safe-area-inset-bottom))
max(8px, env(safe-area-inset-left));
}
#board-space { position: relative; min-width: 0; min-height: 0; }
#board { position: absolute; inset: 0; display: block; width: 100%; height: 100%; touch-action: none; }
.game-hud, .game-controls { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.game-controls button { min-width: 44px; min-height: 44px; }
@media (max-height: 360px) {
#embed-root { gap: 4px; }
.optional-description { display: none; }
}
The middle row gets the space left after the real HUD and buttons. min-height: 0 prevents a grid/flex child from forcing the document taller. overflow: hidden is a final containment rule, not a fix for controls below the screen. Check every essential control remains visible and usable.
On very short landscape frames, simplify the HUD or move controls beside the board. Do not shrink touch targets below 44 CSS pixels to preserve decorative text. If a configuration is unsupported, show a visible rotate/resize instruction with an exit path and disclose the limitation; do not claim that configuration passed.
4. Resize the renderer, not the game state
Observe #board-space with ResizeObserver; its content size excludes the HUD. For a fixed logical board:
const scale = Math.min(availableWidth / designWidth, availableHeight / designHeight);
const offsetX = (availableWidth - designWidth * scale) / 2;
const offsetY = (availableHeight - designHeight * scale) / 2;
Use contain, allowing empty margins. cover crops the board. For canvas 2D, set drawing-buffer width/height to CSS dimensions multiplied by a capped device pixel ratio, then apply a rendering transform. Changing canvas width/height resets its context; redraw existing state rather than creating a new round. Recompute on frame resize and pixel-ratio changes. Disconnect observers/listeners on teardown.
Convert pointer input back to logical coordinates using the canvas bounding rectangle, offsets and scale:
const x = (event.clientX - rect.left - offsetX) / scale;
const y = (event.clientY - rect.top - offsetY) / scale;
// Ignore taps in the letterbox margins, outside [0, designWidth] x [0, designHeight].
For WebGL/Three.js, resize the drawing buffer and update the camera projection; for Phaser/Pixi, use the installed engine version's resize/scale APIs. Do not replace engine sizing with CSS-only stretching. Keep physics, timers, score and random seed intact while resizing.
The ResizeObserver reference describes observing element dimensions. Avoid resize loops: resize the renderer, not the observed container based on its own output.
5. Preserve the SDK connection
Load https://tokenapps.io/sdk/v1.js once in the game document. Register session listeners before ready(). Start the game once for guests even if the SDK fails to load or the host never responds. A session event can repeat after login or token refresh; it must not reset an active run.
SDK v1.4 has no off() method. In a React application use one persistent integration module: register one SDK callback, then fan out to a local subscriber set whose subscriptions your components can remove. Do not register another TokenApps.on() handler on every mount. The package README includes this pattern.
Keep existing score/save/reward calls at their real gameplay boundaries. Changing routes must not submit a score or reward. Do not call track("session_complete") on page load, resize or every render. Platform farming is host-managed and shared with the existing completion award.
There is no SDK resize(), setEmbedMode() or host auto-height message in v1.4. Do not invent calls or request that tokenApps grow the iframe to fit an entire homepage. Implement sizing in the game. An embed query flag is presentation state, not proof of authentication; use the SDK session for identity.
6. Allow framing on the deployed game
Return this HTTP response directive for the game document, merged into the project's existing CSP:
Content-Security-Policy: frame-ancestors 'self' https://tokenapps.io
Remove conflicting X-Frame-Options: DENY or SAMEORIGIN on that document. frame-ancestors must be an HTTP header; a meta tag is not sufficient. Add exact approved staging/partner origins only when needed. Check the final response after redirects; a CDN or hosting rule can override app headers.
Preserve the other CSP directives, allowing the SDK script and any API connections your game uses. CORS and framing permission are separate. Inspect the browser console for blocked frames/scripts/connect requests. See MDN frame-ancestors.
7. Run the reference and test your build
Open the runnable sizing example. It demonstrates a canvas, HUD, touch/keyboard input, contain scaling, SDK session subscription and guest boot. It is a sizing reference, not a replacement game, and sends no scores or reward events. Save its HTML to inspect or adapt the source.
Test your deployed URL in an iframe before submission. A local wrapper exercises layout but does not supply a tokenApps SDK session. For an external HTTPS game, this matches the player's current sandbox permissions:
<iframe
src="https://game.example.com/embed"
title="My game"
style="display:block;width:390px;height:640px;border:0;max-width:100%"
sandbox="allow-scripts allow-forms allow-modals allow-popups allow-popups-to-escape-sandbox allow-pointer-lock allow-orientation-lock allow-presentation allow-same-origin"
allow="fullscreen; accelerometer; gyroscope; autoplay; clipboard-write"
></iframe>
Use an allowed test origin in the game's framing policy. Do not weaken the production policy just to make a wrapper work. Verify the final build in the actual tokenApps preview/player as well.
Test these iframe dimensions in CSS pixels, not just browser window dimensions: 310x360, 380x640, 558x160, 834x220, 740x780, 1340x560 and 1894x875. These are stress cases, not a stable host size contract. Test both guest and signed-in host UI, which may leave different heights. Report unsupported small frames honestly.
In browser developer tools, select the game frame's execution context, then run:
const root = document.documentElement;
console.table({
frameWidth: innerWidth,
frameHeight: innerHeight,
pageWidth: Math.max(root.scrollWidth, document.body.scrollWidth),
pageHeight: Math.max(root.scrollHeight, document.body.scrollHeight),
});
Pass only when the page has no unintended overflow and all controls are visible and clickable. Hidden clipping can pass a scrollbar check while still failing the game. Check internal scroll containers too. Intentional help/settings dialog scrolling is acceptable; gameplay should not require scrolling.
Complete boot, play, pause, resume, game over and replay; then rotate/resize mid-round and verify the same score/state continues. Test touch, keyboard focus, audio unlock, blocked storage, SDK/network failure, guest-to-login transition and denied fullscreen. Include real supported iOS/Android devices; desktop emulation alone does not certify them.
8. Register the URL after deployment
- Deploy
/embedand verify direct access, refresh, framing headers and gameplay. - Submit that exact URL as the primary player URL. Existing games need their registered player URL updated through the review/operator workflow; downloading or saving a device brief does not change it.
- Mobile/PC URLs in the brief are review references only. If using separate builds, the primary URL must route to the right build and preserve embed mode.
- Recheck the actual tokenApps play page. SDK status "Integrated" and a successful header check do not certify responsive layout.
Do not enter https://tokenapps.io/app/your-slug/play as the game's player URL: that embeds the host player inside itself.
Troubleshooting
| Symptom | Inspect and fix |
|---|---|
| Website header/FAQ inside the player | Wrong entry URL or website layout inherited by /embed |
| Scrollbar persists after SDK installation | Inspect the game's root and overflowing containers; SDK does not manage layout |
| No scrollbar, but buttons are missing | Overflow was hidden before fitting HUD/board/controls |
| Board fits but taps hit the wrong location | Undo contain scale and offsets when mapping pointer coordinates |
| Game resets on rotation/login | Engine recreated by resize or repeated session callbacks |
| Blank frame or "refused to connect" | Final URL, HTTPS, CSP, X-Frame-Options, console and runtime errors |
| Works in a tab but stalls inside tokenApps | Storage exceptions, nested iframe, unavailable SDK/auth dependency |
/embed works after navigation but refresh is 404 |
Missing deployed route or SPA fallback |
Copy this into your AI coding tool
Update this existing game for tokenApps embedding. Inspect the current routing,
layouts, engine, SDK initialization and deployment configuration first.
1. Implement a real /embed route (or an equivalent documented game-only URL).
Reuse the existing game logic, assets and save schema. Preserve the public site.
2. Exclude website header/nav/hero/footer/FAQ and long ranking sections from embed.
Keep board, score, lives/coins, pause, sound, restart and all required controls.
3. Fit the iframe's available width AND height. Allocate HUD and controls first,
use ResizeObserver and contain scaling for the board, update canvas/WebGL
resolution and input coordinates. No CSS stretching or hidden clipped buttons.
4. Support mobile touch and PC keyboard/mouse as claimed. Keep touch buttons
>=44 CSS px. Handle short landscape frames, rotation and fullscreen rejection.
Preserve the current round on resize. Report unsupported configurations.
5. Keep SDK v1.4 integration. Register persistent handlers once before ready().
Guest boot must work without SDK/network/storage. Repeated session events
must not restart play. No nested iframe, duplicate SDK or invented resize API.
6. Preserve real score/save/completion behavior. Do not add TAP rewards or send
completion on load/resize. Use platform identity; no second embedded OAuth flow.
7. Deploy the route. Configure HTTP frame-ancestors for https://tokenapps.io and
remove conflicting framing headers on this document. Preserve other CSP rules.
8. Verify iframe sizes 310x360, 380x640, 558x160, 834x220, 740x780,
1340x560, 1894x875. No unintended page scroll, no clipped controls.
Test boot -> play -> pause -> resume -> game over -> replay, resize mid-round,
guest/login, blocked storage, audio and denied fullscreen.
9. Return the deployed game-only URL, changed files, device/browser/build versions,
PC/mobile screenshots, passed/failed/not-tested results and remaining issues.
Do not claim real-device testing without performing it.
Read https://tokenapps.io/dev/sdk and https://tokenapps.io/dev/embed first.
Reference source: https://tokenapps.io/sdk/examples/embed.html