Developers

Documentation

Game SDK

Integrate leaderboards, achievements, save games, inventory, tokens, levels, matchmaking, and user data into your game.

Quick Start

The SDK is automatically injected into every game's HTML when it loads on dmnshd.gg. No script tag needed - dmnshdGameSDK is available as a global.

Call init() once when your game loads. This establishes the connection with the platform.

await dmnshdGameSDK.init();

That's it. You can now use all SDK methods. Every method returns a Promise.

TypeScript Support

Install the npm package for type definitions. The runtime SDK is injected automatically — the package only provides types.

npm install @dmnshd/sdk

Add a triple-slash reference or include @dmnshd/sdk/global in your tsconfig to get the dmnshdGameSDK global typed:

/// <reference types="@dmnshd/sdk/global" />

await dmnshdGameSDK.init();
const user = await dmnshdGameSDK.user.get(); // typed as SDKUser | null

Or import types directly:

import type { SDKUser, LeaderboardEntry } from '@dmnshd/sdk';

Error Handling

All SDK methods return Promises. If a request fails (network error, auth required, invalid input), the Promise rejects with an Error. Requests time out after 10 seconds.

try {
  await dmnshdGameSDK.leaderboard.postScore('default', score);
} catch (err) {
  console.error('Failed to submit score:', err.message);
}

User

GETdmnshdGameSDK.user.get()

Returns the current user or null if not logged in.

const user = await dmnshdGameSDK.user.get();
// { id, username, displayName, avatarUrl, tokens } | null

The tokens field contains the player's current platform token balance. Use this to check if the player can afford an item before calling inventory.purchase().

Tokens

SYNCdmnshdGameSDK.getTokenImageURL()

Returns the absolute URL of the platform's token coin image. Use this to display the token icon in your game's UI (shop screens, price tags, balance displays).

const coinUrl = dmnshdGameSDK.getTokenImageURL();
// "https://cdn.dmnshd.gg/coin.webp"

const img = document.createElement('img');
img.src = coinUrl;
img.width = 24;
img.height = 24;

This is a synchronous method — no await needed. Works before init() is called.

Leaderboards

GETdmnshdGameSDK.leaderboard.get(name, options?)

Fetch entries from a leaderboard. If the leaderboard doesn't exist yet, it will be created automatically when the first score is submitted.

namestring — Leaderboard name (e.g. "default", "speedrun")
options.limitnumber — Max entries to return (default 50)
const entries = await dmnshdGameSDK.leaderboard.get('default', { limit: 10 });
// [{ rank, score, metadata, createdAt, user: { id, username, ... } }]
GETdmnshdGameSDK.leaderboard.getMy(name)

Get the current player's own entry on a leaderboard. Returns null if the player has no entry or is not logged in.

const myEntry = await dmnshdGameSDK.leaderboard.getMy('default');
// { rank, score, metadata, createdAt } | null
GETdmnshdGameSDK.leaderboard.getAroundMy(name, options?)

Get leaderboard entries around the current player's rank. Useful for showing "your neighborhood" on the leaderboard. Returns an empty list if the player has no entry.

namestring — Leaderboard name
options.limitnumber — Number of entries above and below the player (default 5)
const { entries, myRank } = await dmnshdGameSDK.leaderboard.getAroundMy('default', { limit: 3 });
// entries: [{ rank, score, user, ... }]
// myRank: 42 | null
POSTdmnshdGameSDK.leaderboard.postScore(name, score, metadata?)

Submit a score. For BEST leaderboards (default), only updates if the new score beats the player's existing entry. For SUM leaderboards, scores accumulate. The SDK caches the player's best score locally to skip redundant server calls. Leaderboards are auto-created on first submission.

namestring — Leaderboard name
scorenumber — The score value
metadataobject — Optional extra data (e.g. { level: 5 })
const result = await dmnshdGameSDK.leaderboard.postScore('default', 1500, { level: 5 });
// { success, updated, entry: { rank, score, metadata, createdAt } }

Leaderboards support two aggregation modes: BEST (default) keeps only the player's best score, SUM accumulates all submitted scores. Configure this in your game's dashboard.

Save Games

Save and load arbitrary JSON data in numbered slots (0–99). Each slot holds one save per player.

GETdmnshdGameSDK.saveGame.list()

List all save slots the player has used for this game.

const saves = await dmnshdGameSDK.saveGame.list();
// [{ id, slot, data, createdAt, updatedAt }]
GETdmnshdGameSDK.saveGame.load(slot)

Load data from a specific slot. Returns null if the slot is empty.

const data = await dmnshdGameSDK.saveGame.load(0);
// { level: 5, hp: 100, inventory: [...] } | null
POSTdmnshdGameSDK.saveGame.write(slot, data)

Write data to a slot. Creates or overwrites the existing save.

await dmnshdGameSDK.saveGame.write(0, {
  level: 5,
  hp: 100,
  inventory: ['sword', 'potion'],
});
DELETEdmnshdGameSDK.saveGame.remove(slot)

Delete a save slot.

await dmnshdGameSDK.saveGame.remove(0);

Achievements

Define achievements in your game's dashboard, then unlock them at runtime via the SDK. Achievements award points to the player's profile.

GETdmnshdGameSDK.achievement.getAll()

Get all non-secret achievements for this game, including which ones the current player has unlocked.

const { achievements, unlockedKeys } = await dmnshdGameSDK.achievement.getAll();
// achievements: [{ id, key, name, description, iconUrl, points }]
// unlockedKeys: ['first-kill', 'level-up']

Secret achievements won't appear in getAll() but can still be unlocked via unlock().

POSTdmnshdGameSDK.achievement.unlock(key)

Unlock an achievement by its key. Safe to call multiple times — returns the existing unlock if already unlocked. Shows an in-game toast notification on first unlock.

const result = await dmnshdGameSDK.achievement.unlock('first-kill');
// { achievement: { key, name, points, ... }, unlockedAt, alreadyUnlocked }

Inventory

Define items in your game's dashboard, then grant and consume them at runtime via the SDK. Supports stackable items (potions, ammo) and singular items (unique equipment).

GETdmnshdGameSDK.inventory.getCatalog()

Get all items defined for this game. Returns the full catalog regardless of what the player owns.

const items = await dmnshdGameSDK.inventory.getCatalog();
// [{ slug, name, description, thumbnailUrl, price, type, itemClass, metadata, ... }]

The price field is the token cost. Items with price: 0 are free to grant.

GETdmnshdGameSDK.inventory.get()

Get the current player's owned items for this game, including quantities.

const owned = await dmnshdGameSDK.inventory.get();
// [{ slug, name, type, ownedCount, acquiredAt, expiresAt, ... }]
POSTdmnshdGameSDK.inventory.purchase(itemSlug, count?)

Purchase an item for the player. If the item has a token price, the cost is deducted from the player's balance. For stackable items, increments the count. For singular items, only one can be owned.

itemSlugstring — The item's slug from your catalog
countnumber — Quantity to grant (default 1, stackable only)
const result = await dmnshdGameSDK.inventory.purchase('health-potion', 3);
// { success, tokens, item: { slug, name, type, ownedCount, acquiredAt } }
// tokens = updated balance after purchase

Throws an error if the player has insufficient tokens or already owns a singular item.

POSTdmnshdGameSDK.inventory.consume(itemSlug, count?)

Use or consume an owned item. Decrements the count for stackable items. Removes the item entirely if the count reaches zero.

itemSlugstring — The item's slug
countnumber — Quantity to consume (default 1)
const result = await dmnshdGameSDK.inventory.consume('health-potion');
// { success, remaining: 2 }

Levels

Let players create, share, and play each other's levels. Level data is arbitrary JSON (geometry, objects, scripts, etc.) up to 512 KB. Call recordPlay when a level session starts, and submitCompletion when the player finishes — this keeps the completion rate accurate.

GETdmnshdGameSDK.levels.list(options?)

Browse published levels. Returns metadata only — level data is not included in list results.

options.sort"newest" | "popular" | "featured" (default: newest)
options.difficulty"EASY" | "MEDIUM" | "HARD" | "EXPERT"
options.tagsstring[] — Filter by tags (all tags must match)
options.page / options.limitnumber — Pagination (default limit: 20, max: 50)
const result = await dmnshdGameSDK.levels.list({ sort: 'popular', limit: 10 });
// {
//   data: [{ id, slug, name, creatorName, difficulty, tags, likeCount, playCount,
//            completionRate, thumbnailUrl, publishedAt, ... }],
//   pagination: { page, limit, total, pages }
// }
GETdmnshdGameSDK.levels.listMy(options?)

List the current player's own levels across all statuses (DRAFT, PUBLISHED, ARCHIVED). Use this to let players manage their created levels.

options.page / options.limitnumber — Pagination (default limit: 20, max: 50)
const result = await dmnshdGameSDK.levels.listMy({ limit: 10 });
// {
//   data: [{ id, slug, name, status, difficulty, ... }],
//   pagination: { page, limit, total, pages }
// }
GETdmnshdGameSDK.levels.get(id)

Fetch a published level including its full data payload. Only works for published levels.

const level = await dmnshdGameSDK.levels.get(id);
// { ...metadata, data: { /* your level JSON */ } }
GETdmnshdGameSDK.levels.getMy(id)

Fetch one of the current player's own levels, including drafts. Useful for previewing a level before publishing.

const draft = await dmnshdGameSDK.levels.getMy(id);
// Returns level at any status (DRAFT, PUBLISHED, ARCHIVED)
POSTdmnshdGameSDK.levels.create(input)

Create a new draft level. The level is private until publish() is called. Level data must not exceed 512 KB.

gameIdstring — ID of the game this level belongs to
namestring — Level name (max 100 chars)
dataobject — Level content JSON (max 512 KB)
difficultyoptional — "EASY" | "MEDIUM" | "HARD" | "EXPERT" (default: MEDIUM)
tagsstring[] — Up to 10 tags for discoverability
remixedFromIdoptional — ID of a published level this is based on
const level = await dmnshdGameSDK.levels.create({
  gameId: 'abc123',
  name: 'The Floating Ruins',
  data: { tiles: [...], spawnPoint: { x: 0, y: 5, z: 0 } },
  difficulty: 'HARD',
  tags: ['platformer', 'ruins'],
});
// { id, slug, status: 'DRAFT', ... }
PATCHdmnshdGameSDK.levels.update(id, input)

Update a level's fields. Passing a new data value increments the level's version. Owner only.

await dmnshdGameSDK.levels.update(id, {
  name: 'The Floating Ruins v2',
  data: { tiles: [...], spawnPoint: { x: 0, y: 6, z: 0 } },
  tags: ['platformer', 'ruins', 'hard'],
});
POSTdmnshdGameSDK.levels.uploadThumbnail(levelId, image)

Upload a preview image for a level you own (while the game is embedded on dmnshd.gg). Uses a Blob or File — e.g. from canvas.toBlob(), fetch(...).then(r => r.blob()), or a file input. Max 256 KB; JPEG, PNG, or WebP. Returns the updated level including thumbnailUrl.

const level = await dmnshdGameSDK.levels.uploadThumbnail(levelId, imageBlob);
// level.thumbnailUrl → '/uploads/levels/…' (use with your CDN / site origin)
POSTdmnshdGameSDK.levels.publish(id)

Make a draft or archived level publicly visible. Owner only.

const level = await dmnshdGameSDK.levels.publish(id);
// { ...level, status: 'PUBLISHED', publishedAt: '...' }
DELETEdmnshdGameSDK.levels.remove(id)

Soft-delete a level. It becomes invisible to other players but is not permanently removed. Owner only.

await dmnshdGameSDK.levels.remove(id);
POSTdmnshdGameSDK.levels.like(id)

Toggle a like on a published level. Calling again removes the like.

const { liked, likeCount } = await dmnshdGameSDK.levels.like(id);
// liked: true | false
// likeCount: updated total
POSTdmnshdGameSDK.levels.recordPlay(id)

Record that a player started playing a level. Call this when the level loads. This increments playCount and is used to calculate the completion rate — so always call it before submitCompletion.

// Call when the level starts
await dmnshdGameSDK.levels.recordPlay(levelId);
POSTdmnshdGameSDK.levels.submitCompletion(id, input)

Record a completed run. Increments completionCount and updates the completion rate. All fields are optional.

scoreoptional number — Run score
durationMsoptional number — Time to complete in milliseconds
metadataoptional object — Any extra game-specific data
const completion = await dmnshdGameSDK.levels.submitCompletion(levelId, {
  score: 4200,
  durationMs: 93_500,
  metadata: { deaths: 3, coinsCollected: 12 },
});
// { id, levelId, userId, score, durationMs, metadata, createdAt }

Matchmaking

Publish a host's room to a discoverable lobby list so other players can find and join it. Designed to pair with a peer-to-peer signaling server (e.g. PeerJS) — the host advertises its peerCode, the joiner reads it and connects via peer. Lobbies are partitioned per game, expire automatically when the host disconnects, and only the host can mutate or close their lobby.

POSTdmnshdGameSDK.matchmaking.host(opts)

Publish the host's room to the matchmaking list and become its owner. Automatically starts a heartbeat (~30s) to keep the lobby alive; if the player closes the tab or loses connection, the lobby is removed within ~60s. Returns the created lobby. Requires login.

peerCodestring — The room code from your signaling server (e.g. PeerJS peer id). Other players will use this to connect.
maxPlayersnumber — Slot count (2–64). Determines when the lobby is marked 'full'.
nameoptional string — Display name shown in the lobby browser (max 80 chars).
isPrivateoptional boolean — If true, omitted from list() and quickJoin() but still fetchable by id (good for invite-only links).
regionoptional string — Free-form region tag (max 32 chars).
metadataoptional object — Arbitrary JSON (max 2 KB encoded). Useful for game-specific lobby state like map name or game mode.
const lobby = await dmnshdGameSDK.matchmaking.host({
  peerCode: myPeer.id,
  maxPlayers: 4,
  name: 'Sunday casuals',
  metadata: { map: 'forest', mode: 'ctf' },
});
// { id, peerCode, hostUsername, maxPlayers, currentPlayers: 1, status: 'open', ... }

A player can only host one lobby per game; calling host() while another is active closes the previous one first.

PATCHdmnshdGameSDK.matchmaking.update(patch)

Update the currently hosted lobby. Pass any subset of currentPlayers, maxPlayers, name, status, or metadata. currentPlayers is clamped to maxPlayers; status is auto-derived ('open'/'full') unless you set it to 'in_game' or 'closed'.

await dmnshdGameSDK.matchmaking.update({ status: 'in_game' });
await dmnshdGameSDK.matchmaking.update({ currentPlayers: 3 });
PATCHdmnshdGameSDK.matchmaking.setPlayerCount(n)

Convenience shortcut for update({ currentPlayers: n }). Call this whenever a peer connects or disconnects on your host so the lobby browser stays accurate.

peer.on('connection', () => dmnshdGameSDK.matchmaking.setPlayerCount(connectedPeers.size + 1));
DELETEdmnshdGameSDK.matchmaking.close()

Close the host's lobby and stop the heartbeat. Safe to call multiple times. The SDK does this automatically if heartbeats fail (e.g. server-side TTL expired). For graceful shutdown, call this from your game's exit / beforeunload handler.

window.addEventListener('beforeunload', () => dmnshdGameSDK.matchmaking.close());
GETdmnshdGameSDK.matchmaking.list()

Return all public lobbies for the current game. Sorted by current player count descending (almost-full lobbies first), then by creation time. Private lobbies are excluded.

const lobbies = await dmnshdGameSDK.matchmaking.list();
for (const l of lobbies) {
  console.log(`${l.name ?? l.hostUsername}: ${l.currentPlayers}/${l.maxPlayers}`);
}
GETdmnshdGameSDK.matchmaking.quickJoin()

Return the best joinable lobby (most current players that still has open slots), or null if none qualify. Doesn't reserve a slot — connect to the returned peerCode, and the host will reflect your join when their player count updates.

const lobby = await dmnshdGameSDK.matchmaking.quickJoin();
if (lobby) {
  myPeer.connect(lobby.peerCode);
} else {
  // No public lobbies — host one instead
}
GETdmnshdGameSDK.matchmaking.get(id)

Fetch a single lobby by id, including private ones. Returns null if the lobby is expired or doesn't belong to this game. Useful for invite links that carry a lobby id.

const lobby = await dmnshdGameSDK.matchmaking.get(invitedLobbyId);
POSTdmnshdGameSDK.matchmaking.broadcast(opts?)

Announce the host's active lobby in the dmnshd.gg Discord with a one-click join link (/games/<slug>?join=<peerCode>). The user must own an active lobby for this game. Rate-limited server-side to once per (user, game) per 5 minutes.

peerCodeoptional string — Defaults to the peerCode from the lobby you created with host(). Must match an active lobby owned by the caller.
const { success, retryAfterSeconds } = await dmnshdGameSDK.matchmaking.broadcast();
if (!success) showToast(`Try again in ${retryAfterSeconds}s`);

Returns { success: true } on success, or { success: false, retryAfterSeconds } when on cooldown.

LOCALdmnshdGameSDK.matchmaking.currentLobbyId()

Returns the id of the lobby this client is currently hosting (synchronous), or null if none.

Full Example

A minimal game integration. The SDK script is injected automatically — just use the global.

<!DOCTYPE html>
<html>
<head>
  <title>My Game</title>
</head>
<body>
  <script>
    async function main() {
      // Initialize SDK
      await dmnshdGameSDK.init();

      // Check if player is logged in
      const user = await dmnshdGameSDK.user.get();
      if (user) {
        console.log('Welcome back,', user.displayName || user.username);
        console.log('Token balance:', user.tokens);
      }

      // Load saved progress
      const save = await dmnshdGameSDK.saveGame.load(0);
      let level = save ? save.level : 1;

      // ... game logic ...

      // Save progress
      await dmnshdGameSDK.saveGame.write(0, { level: level });

      // Submit score
      await dmnshdGameSDK.leaderboard.postScore('default', 1500);

      // Check your rank and nearby players
      const { entries, myRank } = await dmnshdGameSDK.leaderboard.getAroundMy('default', { limit: 3 });
      console.log('My rank:', myRank);

      // Unlock achievement
      if (level >= 10) {
        await dmnshdGameSDK.achievement.unlock('veteran');
      }

      // Browse the item catalog and check prices
      const catalog = await dmnshdGameSDK.inventory.getCatalog();
      const potion = catalog.find(i => i.slug === 'health-potion');

      // Purchase an item (deducts tokens)
      if (potion && user && user.tokens >= potion.price) {
        const result = await dmnshdGameSDK.inventory.purchase('health-potion', 2);
        console.log('Remaining tokens:', result.tokens);
      }

      // Load and play a UGC level
      const levels = await dmnshdGameSDK.levels.list({ sort: 'popular', limit: 5 });
      const picked = levels.data[0];
      if (picked) {
        const levelData = await dmnshdGameSDK.levels.get(picked.id);
        await dmnshdGameSDK.levels.recordPlay(picked.id);
        // ... load levelData.data into your game engine ...

        // When player finishes:
        await dmnshdGameSDK.levels.submitCompletion(picked.id, {
          score: 3800,
          durationMs: 120_000,
        });
      }
    }

    main();
  </script>
</body>
</html>

Notes

01

The SDK is injected into every .html file served from your game's upload directory. It uses postMessage to communicate with the platform — no direct network requests.

02

The player must be logged in on dmnshd.gg for write operations (scores, saves, achievements, level creation) to work. Read operations like user.get() return null for guests.

03

Leaderboards are created automatically when you submit the first score to a new name. To configure sort order (ascending or descending), create the leaderboard from your game's dashboard first.

04

Achievements must be defined in your game's dashboard before they can be unlocked via the SDK. Secret achievements won't appear in getAll() but can still be unlocked.

05

Save slots are numbered 0–99. Each player gets their own save data per slot, per game.

06

Inventory items must be defined in your game's dashboard before they can be purchased via the SDK. Items with a token price deduct from the player's balance. Stackable items can be accumulated; singular items can only be owned once.

07

Leaderboards support two aggregation modes: BEST (default) keeps only the player's best score, SUM accumulates all submitted scores. Configure this in your game's dashboard.

08

Tokens are the platform's currency. Players buy tokens on dmnshd.gg and spend them on in-game items. Set item prices (in tokens) from your game's dashboard. Items with price 0 are free to grant.

09

Level data is capped at 512 KB per level. The version field increments each time data is updated — use it to cache-bust loaded level content.

10

Always call recordPlay when a level session begins and submitCompletion only when the player actually finishes. This keeps the completion rate meaningful.

11

Matchmaking lobbies expire ~60 seconds after the host stops sending heartbeats (the SDK does this automatically while host() is active). The platform also closes lobbies the instant a host's signaling peer disconnects, so ghost rooms are uncommon.

12

The peerCode you pass to matchmaking.host() should be a code your players can actually connect to via your signaling server (PeerJS or equivalent). When a player clicks a Discord broadcast link, they land on the game page with ?join=<peerCode> in the URL — read it from window.location.search and auto-connect.