GameSDK core reference
- Written for
- + Written for
- Deprecated
- + Deprecated
- Applies to
- + Applies to
GameSDK core reference
GameSDK is the small JavaScript library your game uses to talk to the Cool GPT Games page it is running in. This page covers the core: loading the SDK, the window.GameSDK object, the lifecycle calls (ready, start, gameOver), getPlayer, the event system, and the conventions every SDK method follows.
Feature-specific methods have their own pages:
Area | Methods | Page |
|---|---|---|
Saves |
| |
Scores |
| |
Progression & social |
| |
Ads |
| |
Store |
| |
Tournaments |
| |
Live-ops |
| |
Multiplayer |
| |
Ranked |
|
For the bigger picture (sandbox, origins, play sessions), see How games run.
Loading the SDK
Add one script tag to your index.html, before any script that uses it:
<script src="/sdk/game-sdk.js"></script>
<script src="game.js"></script>Use the relative path
/sdk/game-sdk.jsexactly as written. Your game is served from the game CDN, so this path resolves to the SDK there.The same file is available at
https://cdn.coolgptgames.com/sdk/game-sdk.js. Referencing that absolute URL also passes the upload scanner (it's the same origin your game is served from), but the relative path is recommended.There is nothing to install and no npm package for in-game use. The file is a plain script (not a module) and it creates
window.GameSDKas soon as it runs.Don't add
asyncto the SDK tag unless your own code waits for it. With a normal tag,window.GameSDKexists by the time the next script runs.
Always guard for the SDK being missing, because it won't load when you open the game locally (see Running outside the site):
var SDK = window.GameSDK || null;
if (SDK) SDK.ready();Or define a stub with the methods you use, so the rest of your code doesn't need if checks:
var SDK = window.GameSDK || {
ready: function () {},
start: function () {},
gameOver: function () {},
submitScore: function () {},
saveData: function () { return Promise.resolve({ ok: false, error: "no_sdk" }); },
loadData: function () { return Promise.resolve(null); },
getPlayer: function () { return Promise.resolve({ signedIn: false, level: 1, handle: null }); },
on: function () { return this; }
};Runtimes
The SDK is a plain script, so it works in any runtime where you control the HTML and can run JavaScript.
Runtime | How the SDK gets in |
|---|---|
HTML5 | You add the |
PICO-8, Twine, Bitsy | The site does not add it for you. Your exported HTML is served as-is. To use the SDK, add the same script tag to the exported HTML before zipping, and call |
window.GameSDK
After the script loads, window.GameSDK has these members:
Member | Kind | Documented on |
|---|---|---|
| lifecycle | this page |
| lifecycle | this page |
| lifecycle | this page |
| player | this page |
| events | this page |
| scores | |
| saves | |
| progression | |
| ads | |
| store | |
| tournaments | |
| live-ops | |
| multiplayer | |
| ranked 1v1 |
Conventions
These apply to every SDK method.
Fire-and-forget vs. promises
Methods that only tell the page something (
ready,start,gameOver,submitScore,deleteSave,trackEvent,reportError,social.viewProfile) returnundefined. You get no confirmation that they worked.Methods that ask for something return a
Promise. That includessaveData, which resolves with whether the save landed (you can still ignore it).SDK promises never reject, and they always settle. If something fails, a promise resolves with a fallback value (
null,[],{}, or an object with anerrororreasonfield). Every request has a built-in timeout (see the table below), so no SDK promise is left pending. Don't rely on.catch()for SDK errors. Check the resolved value instead.
Timeouts
Every promise-returning method has a built-in timeout. If the page doesn't answer in time, the promise resolves with a fallback value:
Method | Built-in timeout | Value on timeout |
|---|---|---|
| 5 s | Last known player info. Before any answer, that is |
| 8 s |
|
| 8 s |
|
| 8 s |
|
| 8 s |
|
| 6 s |
|
| 6 s |
|
| 6 s |
|
| 6 s |
|
| 6 s |
|
| 6 s |
|
| 12 s |
|
| 6 s | A local seed string |
| 60 s (the player may be watching the ad) |
|
A late answer that arrives after the timeout is ignored for that call (for getPlayer() it still fires the player event).
listRooms() is a direct network request rather than a message to the page. It resolves null if the request fails. See Multiplayer.
Message rate cap
The page accepts at most 30 SDK messages per second from your game. Messages over the cap are silently dropped. A dropped request never gets an answer, so its promise resolves with the timeout value above, seconds later. Don't call SDK methods every frame. Batch analytics and save on meaningful events, not continuously.
Many SDK calls also become requests from the player's browser to the Cool GPT Games API, which applies its own per-IP limit (100 requests per minute across the whole API) plus per-feature limits. See Errors & limits.
Parallel calls are safe
Every request carries its own request id, and the page echoes it on the answer, so each promise resolves with the answer to its call. You can call any method again (including loadData for the same key) before an earlier call has answered. A few methods treat parallel calls specially:
Method | Parallel calls |
|---|---|
| Share one lookup on the page. |
| Share one request, and resolve with the same object. |
| Only one ad runs at a time. A second call while an ad is running resolves |
Arguments are coerced, not validated
The SDK converts arguments with String(...) / Number(...) where the method needs it, and doesn't throw on bad input. Invalid values are dropped silently further down the line (by the page or the API). Pass the documented types.
No unsubscribe
on() has no matching off(). Register each listener once, at startup.
Running outside the site
The SDK only works when your game runs inside the Cool GPT Games page (on the game's page, or in the site's embed player). It sends every request to the site's origin, https://coolgptgames.com, and ignores messages from any other origin.
Situation | What happens |
|---|---|
You open |
|
The real SDK loads, but the page around it isn't Cool GPT Games (for example, the Preview build button in your dashboard opens the game in its own tab, or someone frames the bundle elsewhere) | Every message is silently dropped. Fire-and-forget calls do nothing. Promises resolve with the timeout values above once their timeouts pass. |
ready()
GameSDK.ready(): voidTells the page your game has loaded and is showing its first screen.
Parameters: none.
Returns: undefined.
What it does:
Hides the site's loading overlay (the spinner over your cover image) immediately.
In the embed player, tells the host page
ready(see Embedding & the score bridge).
If you never call it: the overlay still disappears about 1.2 seconds after your index.html finishes loading. If the frame never finishes loading within about 15 seconds (plus a short runtime-specific warm-up), the page shows "This game failed to load".
Guests / signed-in: same for both. No sign-in needed.
Runtimes: all, as long as the SDK is included.
Limits: none beyond the 30 messages/second cap.
Failure behaviour: silently ignored outside the site.
// After your first frame is drawn and assets needed for the title screen are loaded:
drawTitleScreen();
if (window.GameSDK) GameSDK.ready();Pitfalls:
Call it once your first screen is actually visible. Calling it before loading large assets reveals a blank frame.
Calling it more than once is harmless, but each call sends another
readyto an embedding host page.
start()
GameSDK.start(): voidTells the page that gameplay has begun. This is what starts the play session, and the play session is how plays, play time, XP eligibility, leaderboard eligibility, mid-roll ads and analytics are all measured. See How games run for the full model.
Parameters: none.
Returns: undefined.
What it does (on the first call for this page load):
Asks the site to start a play session for your game. This works for guests too.
Starts a heartbeat every 15 seconds that measures play time.
Sends your game its server-issued replay seed (fires the
replaySeedevent). Only matters if you use verified replays. See Scores, leaderboards & anti-cheat.In the embed player, tells the host page
gamestart.
Later calls on the same page load reuse the same session. They don't start a new one. Calling start() at the beginning of every round is fine and common.
If you never call it (and never call replay.ready(), which also starts a session), there is no play session, so:
Feature | Without a session |
|---|---|
Plays / play time | Not counted |
| Resolves with |
| Nothing is unlocked. Resolves |
| Dropped |
Mid-roll ads ( | Resolve |
| Dropped |
Rewarded ads | Still work (they don't need a session) |
Guests / signed-in: both get a session. Features that record progress (XP, achievements, scores) additionally need a signed-in player.
Limits:
Sessions can only be started for published games.
Starting sessions is limited to 60 per hour per IP address. When the limit is hit, no session is created and nothing tells your game.
Each browser gets at most 20 counted sessions per game per 24 hours. Sessions after that still start, but they're flagged and don't count as plays or earn XP or scores.
Failure behaviour: silent. Your game isn't told whether a session started.
Runtimes: all, as long as the SDK is included.
function beginRound() {
score = 0;
state = "playing";
if (window.GameSDK) GameSDK.start();
}Pitfalls:
Session start is asynchronous. Calls made in the same moment as
start()(for example, an immediatesubmitScore) can arrive before the session exists and be dropped. In practice, XP needs at least 30 seconds of session time and scores need about a minute (see How games run), so award those later in play.Calling
start()on page load for a game with a long menu makes play time include menu time. That's allowed, but call it when play actually begins if you want accurate numbers.
gameOver(score?)
GameSDK.gameOver(score?: number): voidTells the page a run has ended.
Parameters:
Name | Type | Required | Constraints |
|---|---|---|---|
|
| no | Must be a real JavaScript number to be passed on. A string like |
Returns: undefined.
What it does: in the embed player, tells the host page gameover, including score when it's a number. That's all.
What it does not do:
It does not record a score on the leaderboard or in tournaments. Call
submitScore(score)for that (see Scores, leaderboards & anti-cheat). - It does not end the play session. The session ends when the player leaves the game page or closes the tab.It doesn't show any UI. Your game draws its own game-over screen.
Guests / signed-in: same for both.
Runtimes: all, as long as the SDK is included.
Failure behaviour: silently ignored outside the site.
function endRound() {
state = "over";
if (window.GameSDK) {
GameSDK.gameOver(score); // lifecycle signal (and embed score bridge)
GameSDK.submitScore(score); // actually records the score
}
}Pitfalls:
Forgetting
submitScoreis the most common reason "my scores don't show up".Call it once per run. Each call is forwarded to an embedding host page, which may count it.
getPlayer()
GameSDK.getPlayer(): Promise<{ signedIn: boolean; level: number; handle: string | null }>Asks who is playing.
Parameters: none.
Returns: a promise resolving to:
Field | Type | Meaning |
|---|---|---|
|
|
|
|
| The player's platform-wide level (from XP across all games), not a level in your game. |
|
| The player's public handle, without |
Situation | Resolves with |
|---|---|
Guest |
|
Signed in |
|
Signed in, but the lookup failed |
|
No answer within 5 s | The last player info the SDK received (initially |
Failure behaviour: never rejects. It falls back to the last known info after 5 seconds.
Guests / signed-in: works for both.
Limits: for a signed-in player, the first call makes two requests to the API. After a successful lookup the page remembers the answer and replies instantly, and parallel calls share one lookup. A lookup that failed isn't remembered, so the next call tries again. The remembered level stays current as awardXp answers arrive.
Runtimes: all, as long as the SDK is included.
Every answer from the page also fires the player event, including answers that arrive after the 5 second timeout. To handle a slow answer, listen to the event as well:
function showPlayer(p) {
label.textContent = p.signedIn
? "Playing as " + (p.handle ? "@" + p.handle : "a signed-in player")
: "Playing as guest. Sign in to save progress to your account.";
}
GameSDK.on("player", showPlayer); // catches late answers too
GameSDK.getPlayer().then(showPlayer);Pitfalls:
On a very slow connection the first call can still time out and resolve with the default guest info, and the real answer then arrives through the
playerevent. Don't make permanent decisions (like wiping a guest save) based on onegetPlayer()result.The page doesn't push updates when a player signs in or out mid-game. Call
getPlayer()again if you need fresh info (for example, when returning to your menu).handlecan benulleven whensignedInistrue.
on(event, callback)
GameSDK.on(event: string, callback: (payload?: any) => void): typeof GameSDKRegisters a listener for an SDK event.
Parameters:
Name | Type | Required | Constraints |
|---|---|---|---|
|
| yes | One of the event names below. Unknown names are accepted but never fire. |
|
| yes | Receives the event payload (some events have none). |
Returns: GameSDK itself, so calls can be chained.
Behaviour:
You can register several listeners for the same event. They run in registration order.
Exceptions thrown inside your callback are caught and swallowed by the SDK, so an error in one listener won't stop the others. It also won't appear as an uncaught error. Log inside your callback while debugging.
There's no
off().Events fire only in response to messages from the Cool GPT Games page, so outside the site nothing fires.
GameSDK
.on("pause", function () { paused = true; muteAudio(); })
.on("resume", function () { paused = false; unmuteAudio(); });Event reference
Event | Payload | Fires when |
|---|---|---|
none | The game's browser tab becomes hidden | |
none | The tab becomes visible again | |
none | An ad starts showing over your game | |
| An ad finishes or is closed | |
| An ad couldn't be shown | |
| The page answers an | |
| The player's platform level goes up (once per level) | |
| An | |
| The page answers | |
| A | |
result of | A | |
result of | An | |
| The server-issued replay seed arrives |
pause
No payload. The page sends it when the page's own browser tab becomes hidden: the player switches tabs, minimises the window, or locks their phone.
It is not sent:
when an ad is shown (use
adStartedfor that),when the window merely loses focus but stays visible,
when the game first loads.
Stop your game loop, timers and audio. The browser will also slow down or stop requestAnimationFrame in a hidden tab anyway, so the main job is freezing game time so the player doesn't lose a run while away.
resume
No payload. The page sends it when the tab becomes visible again. Resume only what pause stopped. If your game loop measures time between frames, reset its "last frame" timestamp so the gap isn't treated as one huge frame.
var paused = false, last = performance.now();
GameSDK.on("pause", function () { paused = true; });
GameSDK.on("resume", function () { paused = false; last = performance.now(); });
function frame(now) {
var dt = Math.min((now - last) / 1000, 0.1); // also cap dt as a safety net
last = now;
if (!paused) update(dt);
draw();
requestAnimationFrame(frame);
}
requestAnimationFrame(frame);adStarted
No payload. An ad is now showing over your game, either a mid-roll (a short interstitial) or a rewarded ad card. Pause gameplay and mute audio. Nothing else pauses your game during an ad.
adComplete
Payload: { rewarded: boolean }. The ad finished or the player closed it. Resume gameplay and audio.
For a mid-roll,
rewardedis alwaysfalse.For a rewarded ad,
rewardedistrueonly when the reward was confirmed. Grant the in-game reward only when it'strue.
Each ad request ends with exactly one of adComplete or adError, never both. Full details are on Ads & rewards.
adError
Payload: { reason: string }. No ad was shown. reason is one of:
Reason | Meaning |
|---|---|
| The player hasn't accepted ads (or hasn't answered the consent banner yet). |
| No ad was available. |
| A mid-roll was requested before a play session existed (call |
| Another ad is already running. |
| The page didn't finish the ad within 60 seconds (fired by the SDK itself). |
| Something else went wrong. |
Continue the game as if the ad had finished.
xp
Payload: { granted: number, total: number, level: number, leveledUp: boolean }. Fires for every answer the page sends to awardXp(), including answers where nothing was granted (guest player, no session yet, caps reached). It doesn't fire when the call times out. Check granted > 0 before celebrating. See XP, achievements & social.
levelUp
Payload: { level: number }, the player's new platform level. Fires once per level when an XP grant (from awardXp or from an achievement's XP bonus) levels the player up.
GameSDK.on("levelUp", function (e) { showLevelBanner(e.level); });The site already shows its own level-up toast, so an in-game banner is optional.
achievement
Payload: { key: string, name: string | undefined, alreadyHad: false, unlocked: true }. Fires only when an unlockAchievement(key) call newly unlocked the achievement for the player, the same moment the site shows its own toast. It doesn't fire when the player already had it or when nothing was unlocked. See XP, achievements & social.
player
Payload: { signedIn: boolean, level: number, handle: string | null }. Fires every time the page answers a getPlayer() call, including answers that arrive after that call's 5 second timeout. The page doesn't send it on its own (for example, on sign-in).
friendAdded
Payload: { handle: string, status: "outgoing" | "friends" }. Fires only when social.addFriend(handle) succeeded: a request was sent ("outgoing") or the two players are now friends ("friends"). It doesn't fire for "self" or "error". See XP, achievements & social.
tournamentJoined
Payload: what tournaments.join(id) resolved with. Fires only when the join succeeded, never for a null or { error } answer. See Tournaments.
purchase
Payload: what economy.purchase(sku) resolved with (the { ok: true, ... } success object, which includes the alreadyOwned: true case). Fires only when the purchase succeeded, never for an { error } answer or a null timeout. See In-game economy.
replaySeed
Payload: the seed string. Fires when the server-issued replay seed arrives, which happens after start() (or replay.ready()) once a play session exists. It may fire more than once, because each start() call re-sends the seed. It never fires while the platform is re-running your game to verify a replay. See Scores, leaderboards & anti-cheat.
Related
How games run: sandbox, origins, play sessions, guests vs. signed-in players. - Build and publish your first game: a complete example using these calls. - Errors & limits: all platform limits in one place.