Back to publish
Developer Docs

Poplay SDK Integration Guide

A single Markdown-ready guide for game developers integrating leaderboard, cloud save, and realtime rooms.

Poplay SDK Integration Guide

This guide explains the minimum integration required before submitting an HTML5, Canvas, or WebGL game to Poplay.

1. Package Structure

Upload a ZIP package that can run as a static web game.

index.html
assets/
game.js
poplay.config.json

Requirements:

  • Put index.html in the package root.
  • Use relative asset paths.
  • Make sure the game can run inside an iframe.
  • Do not hardcode leaderboard, cloud save, or realtime service URLs.
  • Put poplay.config.json next to index.html.

2. Add the SDK

Add the SDK script in index.html.

<script src="https://poplay.io/sdk/poplay-sdk.js"></script>

Initialize Poplay before calling platform APIs.

await Poplay.init();

3. Player

const player = await Poplay.player.get();

console.log(player.id);
console.log(player.displayName);
console.log(player.locale);

Notes:

  • player.id is a stable anonymous identity persisted by Poplay for the current browser.
  • Reopening the same game keeps the same player.id unless browser storage is cleared.
  • sessionToken can rotate between sessions without changing cloud save ownership.

4. Leaderboard

Declare a leaderboard in poplay.config.json.

{
  "capabilities": {
    "leaderboard": true
  },
  "leaderboards": [
    {
      "id": "global",
      "name": "Global",
      "sort": "desc",
      "keepBest": true
    }
  ]
}

Submit a score after a round ends.

await Poplay.leaderboard.submit("global", {
  score: 12800,
  metadata: {
    level: 7,
    duration: 93.2
  }
});

Read leaderboard entries.

const entries = await Poplay.leaderboard.list("global", {
  limit: 20
});

Read the current player's entry.

const me = await Poplay.leaderboard.me("global");

Rules:

  • Submit scores only after meaningful gameplay events.
  • Do not submit every frame or every second.
  • score must be a number.
  • Keep metadata small.

5. Cloud Save

Declare cloud save in poplay.config.json.

{
  "capabilities": {
    "cloudSave": true
  },
  "save": {
    "slots": ["default", "save/main", "save/profile"],
    "maxBytes": 65536
  }
}

Use Poplay.save.* for a single primary save slot.

Use Poplay.json.* when the game wants multiple JSON documents such as:

  • save/main
  • save/profile
  • save/config

Every JSON document key must be declared in save.slots.

Use Poplay.publicJson.* for game-scoped public JSON documents that are not bound to individual players.

Load save data.

const save = await Poplay.save.get("default");

if (save) {
  restoreProgress(save);
}

Read save metadata.

const meta = await Poplay.save.getMeta("default");

if (meta) {
  console.log(meta.version, meta.updatedAt);
}

Write save data.

const result = await Poplay.save.set(
  "default",
  {
    level: 12,
    exp: 3400,
    skins: ["red", "gold"],
    settings: {
      sound: true
    }
  },
  {
    expectedVersion: meta?.version
  }
});

console.log(result.version, result.updatedAt);

For a first write or overwrite flow, omit expectedVersion.

await Poplay.save.set("default", {
  level: 12,
  exp: 3400,
  skins: ["red", "gold"],
  settings: {
    sound: true
  }
});

Delete save data.

await Poplay.save.delete("default");

Read a JSON document.

const profile = await Poplay.json.load("save/profile");

Read JSON document metadata.

const profileMeta = await Poplay.json.getMeta("save/profile");

Write a JSON document.

await Poplay.json.save(
  "save/profile",
  {
    nickname: "player_name",
    favoriteSnake: "green"
  },
  {
    expectedVersion: profileMeta?.version
  }
);

Delete a JSON document.

await Poplay.json.delete("save/profile");

Equivalent top-level helpers are also available.

const saveDoc = await Poplay.loadJsonDocument("save/main");
const meta = await Poplay.getJsonDocumentMeta("save/main");
await Poplay.saveJsonDocument("save/main", saveDoc, {
  expectedVersion: meta?.version
});
await Poplay.deleteJsonDocument("save/main");

Read public JSON.

const liveConfig = await Poplay.publicJson.load("config/live");

Read public JSON metadata.

const liveMeta = await Poplay.publicJson.getMeta("config/live");

Write public JSON.

await Poplay.publicJson.save(
  "config/live",
  {
    season: 2,
    eventName: "spring"
  },
  {
    expectedVersion: liveMeta?.version
  }
);

Delete public JSON.

await Poplay.publicJson.delete("config/live");

Equivalent top-level helpers are also available.

const publicDoc = await Poplay.loadPublicJsonDocument("config/live");
const publicMeta = await Poplay.getPublicJsonDocumentMeta("config/live");
await Poplay.savePublicJsonDocument("config/live", publicDoc, {
  expectedVersion: publicMeta?.version
});
await Poplay.deletePublicJsonDocument("config/live");

Rules:

  • Save data must be JSON.
  • Default save limit is 64KB.
  • Do not store images, audio, or large level bundles in cloud save.
  • JSON document keys must be declared in save.slots.
  • Include a small schemaVersion field in your save object.
  • Use expectedVersion when you want conflict protection across tabs or devices.
  • Handle HTTP 409 by reloading the latest save before retrying.
  • Public JSON writes currently trust any valid game session for the same game. Add developer-only authorization before using this in production.

6. Realtime Rooms

Declare realtime rooms in poplay.config.json.

{
  "capabilities": {
    "realtime": true
  },
  "realtime": {
    "maxPlayers": 2,
    "visibility": "public",
    "hostStrategy": "close-on-host-leave"
  }
}

Quick match.

const room = await Poplay.rooms.quickMatch({
  maxPlayers: 2
});

Create a public room.

const room = await Poplay.rooms.create({
  maxPlayers: 2,
  visibility: "public"
});

Join a room.

const rooms = await Poplay.rooms.list();
const room = await Poplay.rooms.join(rooms[0].id);

Send realtime messages.

room.broadcast("player-state", {
  x: player.x,
  y: player.y,
  direction: player.direction
});

room.on("message", (message) => {
  if (message.event === "player-state") {
    applyRemotePlayerState(message.playerId, message.payload);
  }
});

Important:

  • Poplay provides room creation, joining, presence, and message transport.
  • Your game still needs to implement gameplay synchronization.
  • Your game decides player state, world state, host authority, disconnect handling, and match settlement.
  • Keep realtime payloads small, ideally below 4KB.

7. Complete Config Example

{
  "capabilities": {
    "leaderboard": true,
    "cloudSave": true,
    "realtime": true
  },
  "leaderboards": [
    {
      "id": "global",
      "name": "Global",
      "sort": "desc",
      "keepBest": true
    }
  ],
  "save": {
    "slots": ["default"],
    "maxBytes": 65536
  },
  "realtime": {
    "maxPlayers": 2,
    "visibility": "public",
    "hostStrategy": "close-on-host-leave"
  }
}

8. Submission Checklist

Before submitting:

  • index.html exists at the package root.
  • The SDK script is included.
  • Poplay.init() is called before platform APIs.
  • poplay.config.json matches the platform features used by the game.
  • Leaderboard, save, and realtime calls use Poplay APIs.
  • The game runs inside an iframe.
  • Asset paths are relative.
  • The game does not contain third-party platform branding or external game portal links.