Install
Pick the import style that fits your build.
The SDK ships in three formats from one build:
| Format | Best for | Entry inside dist/ |
|---|---|---|
| ESM | Vite, Webpack, Rollup, esbuild, Bun, Deno | dist/index.js + dist/index.d.ts |
| CJS | Node-style require, legacy bundlers | dist/index.cjs + dist/index.d.cts |
| IIFE | A single <script> tag — no build step | dist/gamee-sdk.iife.js |
There are no peer dependencies.
1 — Local build, linked via package.json (recommended today)#
This is the path most game projects use right now. You build the SDK once, then
your game’s package.json points at the built folder.
a) Download the prebuilt SDK#
Grab the ready-to-link SDK folder from GitLab — no checkout, no build step:
The build/ folder is the same payload as packages/sdk/dist/, just
surfaced at the top of the repo so you don’t have to dig through four
nested folders to find the SDK output. Drop it somewhere your project can
reference (e.g. a vendor/ directory). At the top level you’ll find:
build/
├── index.js # ESM entry
├── index.cjs # CJS entry
├── index.d.ts # ESM type declarations
├── index.d.cts # CJS type declarations
├── gamee-sdk.iife.js # IIFE bundle (window.Gamee)
└── *.map # source maps
b) Link from your game#
Pick one of the two patterns below.
Pattern A — file: dependency (simplest, copies on install).
// your-game/package.json
{
"dependencies": {
"@gamee/sdk": "file:../gamee-sdk/packages/sdk"
}
}
npm install
Works with npm, pnpm, yarn. The whole packages/sdk/ folder (including dist/)
is copied into your node_modules. Re-run npm install after every SDK
rebuild to refresh.
Pattern B — copy build/ into your repo (no Node toolchain on the game side).
mkdir -p your-game/vendor/gamee-sdk
cp -R gamee-sdk/build/* your-game/vendor/gamee-sdk/
// your-game/package.json
{
"dependencies": {
"@gamee/sdk": "file:./vendor/gamee-sdk"
}
}
Commit vendor/gamee-sdk/ so the build is reproducible without network access.
c) Use it#
After either pattern, the import is the same in every recipe and example on this site:
import { gamee } from '@gamee/sdk';
await gamee.init({ capabilities: ['saveState'] });
Need a fresh, isolated instance (tests, multiple games on one page)?
import { createSdk } from '@gamee/sdk';
const sdk = createSdk({ allowedOrigins: ['https://games.gameeapp.com'] });
await sdk.init({ capabilities: [] });
CommonJS works too:
const { gamee } = require('@gamee/sdk');
gamee.init({ capabilities: [] }).then(() => gamee.gameReady());
2 — Self-hosted IIFE script tag (window.Gamee)#
For prototypes, level editors, or games that have no build step. Grab
gamee-sdk.iife.js from the repo’s build/
folder and serve it from your own static host. The IIFE bundle attaches a
Gamee global to window:
<!doctype html>
<html>
<head>
<script src="/vendor/gamee-sdk/gamee-sdk.iife.js"></script>
</head>
<body>
<canvas id="game"></canvas>
<script>
// `Gamee` is the same surface as the package.
// Use `Gamee.gamee` (singleton) or `Gamee.createSdk(...)`.
Gamee.gamee.init({ capabilities: [] }).then(() => {
Gamee.gamee.gameReady();
});
</script>
</body>
</html>
The global is
Gamee(capital G). The lowercaseGamee.gameeis the default singleton;Gamee.createSdkandGamee.GameeErrorlive on the same namespace.
3 — npm registry (preview — not live yet)#
# Future. Does not work today.
npm install @gamee/sdk
# pnpm add @gamee/sdk
# yarn add @gamee/sdk
Once published, the same approach will also unlock the ESM-from-CDN form for no-build setups:
<script type="module">
// Future. Does not work today.
import { gamee } from 'https://esm.sh/@gamee/sdk';
</script>
TypeScript#
The SDK is written in TypeScript and ships its own .d.ts and .d.cts files.
No separate @types/... install is needed — types resolve automatically as
soon as your editor sees node_modules/@gamee/sdk/dist/index.d.ts (or the
.d.cts for CJS projects).
Recommended tsconfig.json#
The SDK targets ES2020 and ships ESM by default. The minimum settings:
{
"compilerOptions": {
"target": "ES2020",
"module": "ESNext",
// "bundler" works for Vite/esbuild/Webpack/Rollup. Use "node16" if you
// run TypeScript directly under Node.js without a bundler.
"moduleResolution": "bundler",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"lib": ["ES2020", "DOM", "DOM.Iterable"],
},
}
If moduleResolution is set to the legacy "node", switch to "bundler" or
"node16" — otherwise the type bundler may not pick up the package’s
exports map and you’ll get spurious “cannot find module” errors.
Importing types#
Every public type is re-exported from the package root. Pull them in alongside the runtime imports:
import { gamee, createSdk, GameeError } from '@gamee/sdk';
import type {
// Lifecycle
SdkOptions,
InitOptions,
GameInitResponse,
GameOverData,
// Domain
Capability,
Platform,
Environment,
GameContext,
MissionData,
PurchaseDetails,
PurchaseResult,
RewardedLoadResult,
RewardedResult,
// Events
GameeEvents,
GameeEventName,
// Errors
GameeErrorCode,
// Bridge (for tests / custom transports)
PlatformBridge,
BridgeListener,
} from '@gamee/sdk';
Typing your own handlers#
Event payloads are derived from the GameeEvents map, so handlers infer their
argument shape automatically:
// `payload` is inferred as `{ gameSeed?: string; resetState?: boolean }`
gamee.on('start', (payload) => {
if (payload.resetState) clearLocalProgress();
startRun(payload.gameSeed);
});
// Unknown event names fail to compile:
gamee.on('not-a-real-event', () => {}); // ❌ TS2769
If you want to extract a payload type by name:
import type { GameeEvents } from '@gamee/sdk';
type StartPayload = GameeEvents['start'];
// → { gameSeed?: string; resetState?: boolean }
Narrowing the unknown event fields#
Two events carry unknown so the SDK doesn’t lock you into a platform shape:
avatarUpdate—{ avatar: unknown }miningEventUpdate—{ miningEvent: unknown }
Cast in the handler with whatever type matches your platform’s contract:
interface AvatarPayload {
url: string;
rarity: 'common' | 'rare';
}
gamee.on('avatarUpdate', ({ avatar }) => {
const a = avatar as AvatarPayload;
ui.setAvatar(a.url);
});
Save state and init data are strings#
initResponse.saveState and initResponse.initData are typed as
string | undefined — the platform hands you serialized JSON. Parse defensively:
interface Save {
level: number;
gold: number;
}
const ctx = await gamee.init({ capabilities: ['saveState'] });
const save: Save | null = ctx.saveState ? (JSON.parse(ctx.saveState) as Save) : null;
The corresponding write side accepts unknown and stringifies for you:
gamee.gameSaveState({ level: 4, gold: 200 } satisfies Save);
Typing errors#
GameeError has a typed code field. Exhaustive switch is the idiomatic
recovery pattern:
import { GameeError, type GameeErrorCode } from '@gamee/sdk';
function handle(err: unknown): void {
if (!(err instanceof GameeError)) throw err;
const code: GameeErrorCode = err.code;
switch (code) {
case 'CAPABILITY_MISSING':
/* … */ break;
case 'BRIDGE_TIMEOUT':
/* … */ break;
case 'BRIDGE_ERROR':
/* … */ break;
case 'BRIDGE_OVERFLOW':
/* … */ break;
case 'VALIDATION':
/* … */ break;
case 'INVALID_STATE':
/* … */ break;
case 'NOT_SUPPORTED':
/* … */ break;
}
}
TypeScript with the IIFE script tag#
The IIFE bundle attaches window.Gamee at runtime. To get types in a
TypeScript project that also uses the script-tag form, declare the global:
// src/types/gamee.d.ts
import type * as GameeNs from '@gamee/sdk';
declare global {
interface Window {
Gamee: typeof GameeNs;
}
const Gamee: typeof GameeNs;
}
export {};
You still need @gamee/sdk installed (option 1) for the type imports — but the
runtime entirely comes from the <script> tag.
Verifying the install#
A 5-line health check you can paste into any new game:
import { gamee, SDK_VERSION, ALL_CAPABILITIES } from '@gamee/sdk';
console.log('SDK', SDK_VERSION, 'platform:', gamee.getPlatform());
console.log('declarable capabilities:', ALL_CAPABILITIES);
If getPlatform() returns 'web' and you are running outside the GAMEE platform,
that’s expected — see the architecture page for what changes
on iOS and Android.