Install

Download the SDK, link it from your game.

Two steps: grab the zip, point your game at it. No registry, no network at runtime — the build is reproducible and the types come along for the ride.

1 — Download the SDK#

Unzip it into your project — anywhere your build can reference. A common location is your-game/vendor/gamee-sdk/. At the top level you’ll find:

gamee-sdk/
├── 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)

The SDK ships three formats from one build so you can drop into Vite, Webpack, Rollup, esbuild, Bun, Deno, plain Node require, or a single <script> tag — no peer dependencies in any case.

Need an older release? Every tagged version is browsable at gitlab.gamee.io/gamee/gamee-sdk/-/tags — pick a tag and grab the build/ folder from that tree.

Pick the import style that matches your project.

Option A — package.json (file: dependency)#

For bundler projects (Vite, Webpack, Rollup, esbuild, Bun, Deno) and Node-style require.

// your-game/package.json
{
  "dependencies": {
    "@gamee/sdk": "file:./vendor/gamee-sdk"
  }
}
npm install
import { gamee } from '@gamee/sdk';

await gamee.init({ capabilities: ['saveState'] });

Works with npm, pnpm, yarn. Re-run npm install after every SDK refresh. Commit vendor/gamee-sdk/ so the build is reproducible without network access.

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());

Option B — <script> tag (window.Gamee, no build)#

For prototypes, level editors, or games that have no build step. Serve gamee-sdk.iife.js from your own static host; the 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 lowercase Gamee.gamee is the default singleton; Gamee.createSdk and Gamee.GameeError live on the same namespace.

Working from a local monorepo checkout#

If you’d rather track master directly, clone the monorepo and link straight at the source — no zip in between.

git clone https://gitlab.gamee.io/gamee/gamee-sdk.git

The SDK source lives under packages/sdk/ (the rest of the repo — the playground, the docs site, the test utils — isn’t needed at runtime). Point your game’s package.json at it:

// your-game/package.json
{
  "dependencies": {
    "@gamee/sdk": "file:../gamee-sdk/packages/sdk"
  }
}

Run npm run sdk:build in the monorepo after every pull so packages/sdk/dist/ is fresh, then re-run npm install in the game.

What about npm?#

# 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).

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 A) 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.platform);
console.log('declarable capabilities:', ALL_CAPABILITIES);

If gamee.platform is 'web' and you are running outside the GAMEE platform, that’s expected — see the architecture page for what changes on iOS and Android, and Utilities for the full introspection API.