Skip to main content

Saves for Games on Arcadible

Each version of your game has its own origin, so its browser storage starts empty with every update. Saves made through the Arcadible SDK are kept by the play page instead, so they carry over from one version to the next. They're kept on the player's device.

A save belongs to one player, in one browser or the desktop app: other players never see it, and it doesn't follow the player to another device. For data players share, such as scores or a shared world, use your own server. See Networking.

The Arcadible SDK is optional, and saves are all it does. Installing it, a full example, and its codes are in Arcadible SDK, and every call in arcadible.saves. This page is how to use saves well.

Add the SDK​

Each kind of game has one way in:

Your gameAdd
A build step, like the Vite engine startersThe package: npm install @arcadible/sdk
No build step, like the blank starterThe hosted script, <script src="/arcadible/sdk/v1.js"></script>
An engine's web export, such as Unity or GodotNot supported: the Arcadible SDK doesn't currently have a plugin for engines

How to add each is in Install. arc serve runs your game locally with either, as Arcadible does; under another server, see Locally.

Save and load​

A save is a name holding one string. Serialize it yourself:

// at startup: once the saves have loaded, the player's progress, or a new game.
await arcadible.ready;
const saved = await arcadible.saves.read("progress");
const progress = saved ? JSON.parse(saved) : { level: 1 };

// at a checkpoint:
await arcadible.saves.write("progress", JSON.stringify(progress));
  • Start with arcadible.ready, then load with read. ready settles once the saves have loaded; read would wait for it anyway, and also waits for the saves when they're late.
  • Keep what must stay consistent in one save. Each save is written whole, at once, and saves never mix, so a level and the coins earned in it can't be torn apart. Use a save for progress, one for settings, and one for each slot, not one for each value.
  • Save at checkpoints, and when the page is hidden, on visibilitychange and pagehide. A write resolves once the save is kept on the device, and readSync shows it at once, in the same tick, even before then. When a player closes the tab, the last write may not finish, so don't wait for the end to save. See Pause, save, and resume.
  • Moving from localStorage, gather its keys into one object, and write it as one save.

A failed read isn't a missing save. When the saves can't be reached, read fails with timeout or unavailable. Don't start a new game and save over the player's progress: say so, and try again.

Read during play​

arcadible.ready settles once, when the play page has answered and your game's saves have loaded, or after 10 seconds if they haven't. It never fails. From then on, the sync calls, readSync, listSync, and infoSync, answer at once from memory, so a read in the middle of a frame doesn't wait, and can't fail with busy or timeout:

// during play, after the startup above:
const settings = JSON.parse(arcadible.saves.readSync("settings") ?? "{}");
  • Call anything before it. Calls made earlier wait for it. Only the sync calls throw before it, with not_ready.
  • If the saves are late, taking over 10 seconds to load, ready settles anyway, and the sync calls throw unavailable until they arrive, rather than answer as if there were no save. read waits for them.
  • arcadible.build says what's running: its version, from arcadible.toml or package.json, and its channel. It's null off Arcadible.

Handle other versions' saves​

Every version of your game shares its saves, so each version must read what the others wrote. The play page stamps each save with the version that wrote it, which infoSync returns:

const info = arcadible.saves.infoSync("progress");
// isNewer is your own semver comparison.
const fromNewer =
info?.version && isNewer(info.version, arcadible.build?.version);

Migrate older saves. Only your game knows its format, so read a save from an older version and update it.

Never overwrite a save from a newer version. Every version stays playable at its link, so it happens after you release an older version again, or when a player opens an older version's link. Play on without writing, or ask the player to update.

Channels​

production and staging keep separate saves, so a prerelease can't touch players' progress. A version link uses its build's channel.

build.channel is a string, and other values may be added. Don't assume it's always one of the two.

Two tabs​

A player can open your game in two tabs or windows at once. Each sees the other's saves as soon as they're kept, and the last write wins. Your game isn't told when the other tab saves.

  • Read before you write. Build each save from readSync at the moment you save, not only from what you loaded at startup, or one tab overwrites the other's progress.

When a call fails​

A call that fails rejects with an ArcadibleError. Act on its code, not its message:

  • Worth retrying: busy, timeout, and unavailable. A write that timed out may still have been kept.
  • Not worth retrying: invalid, too_large, and too_many, which mean the save breaks a limit, and full, which means the device has no room left. Tell the player.
  • A code you don't know: treat it as unavailable. New codes may be added.

Every code is in the reference.

Where saves are kept​

The play page keeps saves in its own browser storage, on the player's device. Arcadible doesn't currently keep them in the player's account, so they follow the rules of browser storage:

  • Per browser and per app. A save in one browser isn't in another, or in the Arcadible desktop app.
  • Cleared with the site's data. Clearing play.arcadible.com's data clears every game's saves.
  • Evictable. Browsers can clear them when space runs low. Safari clears them after seven days of use without a visit to Arcadible (WebKit).
  • Private windows keep them until the window closes.

Saves don't need the network: a game keeps saving offline for as long as it's open.

Who can read them back​

saves.where says, once ready has settled:

  • "game": every version of your game can read them. The usual case, on Arcadible and under arc serve.
  • "memory": the browser won't let the play page keep them, such as when it blocks site data, so they last only until the page closes. Tell the player their progress won't be kept.
  • "build": only this build can read them. Off Arcadible, it's the package standing alone. On Arcadible, it means the build was opened directly at its arcadible.io address, or the play page didn't answer within about a second, so the game kept its saves on its own origin, where the next version can't see them. It's rare, and a reload fixes it; the SDK says so in the console.
await arcadible.ready;
if (arcadible.saves.where === "memory") {
showNotice("Progress won't be kept after you close the game.");
}

More values may be added. Treat a value you don't know as "game".

Locally​

arc serve runs your game in a local play page, so saves work as on Arcadible, from the package or the hosted script. build is { version, channel: "local" }, and the local page keeps saves apart from Arcadible's, carrying them over from one build to the next. See arc serve's SDK section. With --url, the page your dev server serves gets the same saves through the package; the dev server doesn't serve the hosted script.

Under another server, such as your engine's dev server:

  • The package stands alone, with the same rules and limits. There, build is null, saves aren't stamped (info().version is null), and every project on the same origin, such as localhost:5173, shares one set of saves.
  • The hosted script isn't found, and arcadible is undefined. To run the game there too, guard each use with if (window.arcadible).

Checklist​

  • Adds the SDK the way its kind of game does, and runs locally under arc serve
  • Starts with arcadible.ready, loads with read, and doesn't start over when the saves can't be reached
  • Uses the sync calls only after arcadible.ready
  • Tells the player when saves.where is "memory"
  • Keeps what must stay consistent in one save
  • Saves at checkpoints and when hidden, without waiting for the player to leave
  • Migrates older versions' saves, and never overwrites a newer version's
  • Reads a save before writing it
  • Treats a code it doesn't know as unavailable
  • Keeps its saves across an update on staging: deploy a prerelease, save, deploy the next, and load

Next steps​

  • arcadible.saves: every call, what it returns, and when it fails.
  • Arcadible SDK: installing it, a full example, ready and build, and every code.
  • Limits: how much a game can save.