Contents
TutorialJavaScript

Install The JavaScript SDK And Save Your First Game Data

Install @persistlyapp/sdk, configure PersistlyGameSaves, load local data, save one default game save, and sync safely.

This is the smallest useful JavaScript integration: configure the SDK, load one default game save, save local data, and sync it when the game reaches a safe moment.

Use this when your game is a browser game, Vite app, Phaser game, PlayCanvas game, Cocos Creator game, or another JavaScript-based wrapper.

Small idle, incremental, and casual games can start with saveData and loadData. If the game later needs manual saves, campaigns, or multiple slots, switch those calls to saveSlot(slotId) and loadSlot(slotId).

Install The SDK

Install the public package from npm:

terminal
npm install @persistlyapp/sdk

Import the high-level game-save facade:

typescript
import { PersistlyGameSaveStatus, PersistlyGameSaves } from "@persistlyapp/sdk";

Use PersistlyGameSaves for normal game code. The lower-level PersistlyClient exists for advanced wrappers and direct runtime API work.

Configure The SDK

Configure once when your game boots:

typescript
await PersistlyGameSaves.configure({
  runtimeKey: "ps_test_replace_me",
});

What this does:

  • runtimeKey selects your Persistly project and environment.
  • ps_test_ keys should be used during development and QA.
  • In browsers, the SDK uses Persistly's built-in local storage adapter by default so it can keep local drafts, account session data, and slot mapping.

Load Before Starting The Game

Read local game data before you start gameplay:

typescript
const loaded = await PersistlyGameSaves.shared.loadData();

if (loaded.status === PersistlyGameSaveStatus.LocalFound && loaded.data) {
  startGameFromState(loaded.data);
} else {
  startNewGame();
}

This is intentionally local-first. The game can start quickly even if the player is offline.

Make The First Save

Save a small JSON-compatible data object:

typescript
await PersistlyGameSaves.shared.saveData({
  level: 1,
  coins: 50,
  checkpoint: "forest-gate",
});

saveData writes local gameplay data immediately. It uses the default autosave slot under the hood and does not require a network request to protect current progress.

Add SlotInfo For Menus

Use slotInfo for slot-select screens, save-slot lists, and support context:

typescript
await PersistlyGameSaves.shared.saveData(
  {
    level: 1,
    coins: 50,
    checkpoint: "forest-gate",
  },
  {
    slotInfo: {
      label: "Autosave",
      chapter: "Forest",
      lastPlayedAt: new Date().toISOString(),
    },
  },
);

Keep real gameplay data in the data object. Keep display/search fields in slotInfo.

Sync Safely

For development, call forceSyncData when you want to prove the cloud path works:

typescript
const result = await PersistlyGameSaves.shared.forceSyncData();

if (result.status === PersistlyGameSaveStatus.Synced) {
  console.log("Synced to Persistly.");
}

if (result.status === PersistlyGameSaveStatus.Conflict) {
  showConflictRecovery();
}

For normal gameplay, call syncDue from safe lifecycle moments:

typescript
document.addEventListener("visibilitychange", () => {
  if (document.visibilityState === "hidden") {
    void PersistlyGameSaves.shared.syncDue();
  }
});

window.addEventListener("online", () => {
  void PersistlyGameSaves.shared.syncDue();
});

syncDue respects the runtime sync policy returned by the API. With the default policy, remote sync is throttled by a minimum interval instead of sending a cloud request for every local save. forceSyncData is for explicit one-save moments such as manual save, checkpoint, pause, or release testing.

Update The Same Save

Call saveData again:

typescript
async function onCheckpointReached(gameState: GameState) {
  await PersistlyGameSaves.shared.saveData({
    level: gameState.level,
    coins: gameState.coins,
    checkpoint: gameState.checkpointId,
    updatedAt: new Date().toISOString(),
  });

  void PersistlyGameSaves.shared.syncDue();
}

Same default save, same logical player progress. New data replaces the local draft for the default save.

One-Save Game

Many idle, incremental, and small single-player games only need one slot:

typescript
await PersistlyGameSaves.shared.saveData(gameState);
const loaded = await PersistlyGameSaves.shared.loadData();

Use this for games where the player always continues the same run.

Multiple Slots Or Manual Slots

If your game has multiple slots or manual save slots, use stable slot keys:

typescript
await PersistlyGameSaves.shared.saveSlot("warrior", warriorState, {
  slotInfo: { characterName: "Borin", className: "Warrior", level: 12 },
});

await PersistlyGameSaves.shared.saveSlot("mage", mageState, {
  slotInfo: { characterName: "Ayla", className: "Mage", level: 9 },
});

const slots = await PersistlyGameSaves.shared.listSlots();

Persistly stores each named slot under the player account. Your game can use listSlots to build a slot-select or load-game screen.

Account-First Games

Games with account login, shared inventory, premium balances, or cross-device restore can use account helpers in addition to game data and slots. That path still uses the same facade, but it adds explicit account/session handling when your backend needs to restore a player on another browser or device.

What To Test Next

  • Save while offline, reload the page, and confirm loadData restores local data.
  • Go online and call forceSyncData.
  • Save the same data twice and confirm the latest local data loads.
  • Add a second slot and confirm listSlots can show both.
  • Add conflict handling before shipping cross-device resume.