HELIX Docs
Platform API

Data Store

Durable, authoritative per-world key–value storage. The source of truth for anything that must survive a session.

Helix.dataStore is HELIX's durable key–value store, scoped per world. It's the right place for anything that must outlive an instance: player progress, settings, world state. If you know Roblox, this is DataStore.

Data Store vs Memory Store

Data Store is durable and authoritative — the source of truth. Memory Store (planned, not shipped) is the volatile, always-expiring counterpart. Leaderboards and matchmaking already ship as Helix.leaderboard and Helix.matchmaking. When in doubt about where data belongs, ask: "would losing this be a bug?" If yes, it's Data Store.

API

Prop

Type

A rejected call throws a HelixRequestError whose code says why: 'rate-limited', 'version-conflict', or a key-policy rule such as 'session-sub-key'. Outside the shell, reads resolve null (or no keys) and writes are no-ops.

Who may write which key

A player's own session may write exactly one key: player:<their user id>. Put the whole save in that one JSON document. Every other key is written by the game server (the room) or creator tooling, and a session write anywhere else is refused with a 403 naming the rule.

KeyWritten by
player:<userId>that player's session · the room · creator tooling
player:<userId>:*the room · creator tooling
world, world:*the room · creator tooling
mp:*the room (persisted multiplayer vars) · creator tooling
devices:<placementId>any visitor in the world · the room · creator tooling
leaderboard:<board>the leaderboard routes (Helix.leaderboard)
helix:*the platform only
mail:*, trade:*, guild:*, party:*, index:*, log:*reserved
anything elsethe room · creator tooling

Example

type Save = { level: number; coins: number };

const { user } = await Helix.init();
if (user) {
  const key = `player:${user.id}`;

  // load on join
  const save = (await Helix.dataStore.get<Save>(key)) ?? { level: 1, coins: 0 };

  // save on change
  save.level += 1;
  await Helix.dataStore.set(key, save);
}

Semantics & guarantees

  • Per-world namespace. Keys are isolated to your world; you can't read another world's data.
  • Authoritative. Writes are durable once set resolves.
  • Write from the server for value-bearing data. Persisting currency/inventory-adjacent state belongs to server-authoritative code — never let a client write its own balance. See the golden rule.
  • No direct database access. Creator code reaches persistence only through this API, never a raw DB connection.

Concurrency

Two instances of the same world can run at once. For counters or state edited by many players, read with getEntry and pass its version back as expectedVersion, so a conflicting write is rejected with 'version-conflict' instead of clobbering. For counters many players bump (community totals, votes, boss HP), the game server uses the atomic increment route (POST /api/v1/data-store/:key/increment); a player session can't.

Limits & quotas

Data Store is free for every world — no subscription required. Generous per-world quotas keep one world from degrading the service (or running up the bill); they're modelled on Roblox DataStore norms and can be raised for your world if you outgrow them:

Prop

Type

Exceeding a storage quota fails the write with a clear error; exceeding the write rate returns RateLimited — back off and retry. Reads aren't rate-limited at the API. Batch writes where you can, and keep hot, high-frequency counters in the room rather than writing them every tick. A player save should be event-driven (on level up, on purchase), not a heartbeat — 30/min is plenty for that.

Data Store quotas ≠ declared-state ceilings

The quotas above govern the Helix.dataStore API. Declared multiplayer state — vars marked persistent: true in the world manifest — is a separate system with its own, much smaller publish-time ceilings: 8 KB per player's persisted document (persistedPlayerBytes), 32 KB per persisted room document (persistedRoomBytes), and at most 6 persisted room documents (persistentRoomKeys). Those are static worst-case caps computed from the declared types and enforced by the manifest validator when a build is published — deliberately far under the 256 KB value limit here, because persisted documents flush through the same shared write budget. Hitting a manifest ceiling is a publish-time declaration problem, not a Data Store quota — and raising a Data Store quota won't move it.

Reference

  • Web SDK → Helix.dataStore
  • REST → GET/PUT/DELETE /api/v1/data-store/:key, POST /api/v1/data-store/:key/increment and GET /api/v1/data-store?prefix= — the world is identified by the world-session token, never a path parameter, so a world can only ever reach its own namespace.
  • Guide → Persist player data

On this page