Skip to content

DataStore

A named store of saved values, read and written by key.

Every method is asynchronous and can fail (a network problem, a limit, too many requests): call them inside task.spawn or an async handler, and catch errors. Keys are 1–50 characters; use player.userId for per-player data. Values are JSON: numbers, strings, booleans, null, arrays and plain objects, up to 64 KB serialized. To save a Vector3 or Color3, store toArray() or toHex().

get<T extends DataValue = DataValue>(key: string): Promise<T | undefined>

The value stored under key, or undefined if there’s none.

set(key: string, value: DataValue): Promise<void>

Stores value under key, replacing what was there.

update<T extends DataValue = DataValue>(key: string, fn: (current: T | undefined) => T | undefined): Promise<T | undefined>

Read-modify-write without lost updates across servers. Return undefined to leave it as is.

fn receives the current value (or undefined) and returns the new one; return undefined to leave it as it is. If another server writes the key in between, fn runs again with the new value, so keep it free of side effects. After 5 conflicting tries the promise rejects. Resolves with the stored value.

increment(key: string, delta?: number): Promise<number>

Adds delta (default 1) to the number stored under key and resolves with the result; a missing key counts as 0. Safe when several servers increment at once. Rejects if the stored value isn’t a number.

remove(key: string): Promise<void>

Deletes key.

import {
DataStoreService,
NumberValue,
Player,
Players,
createLeaderstats,
task,
warn,
} from '@rally/sdk';
type Progress = { v: 1; level: number; gold: number };
const progress = DataStoreService.getDataStore('Progress');
const loaded = new Map<Player, { level: NumberValue; gold: NumberValue }>();
Players.playerAdded.connect((player) => {
const stats = createLeaderstats(player, { Level: 1, Gold: 0 });
task.spawn(async () => {
try {
const saved = await progress.get<Progress>(player.userId);
if (saved) {
stats.Level.value = saved.level;
stats.Gold.value = saved.gold;
}
// Only save players whose data loaded, so a failed load never overwrites saved data.
loaded.set(player, { level: stats.Level, gold: stats.Gold });
} catch (e) {
warn(`couldn't load ${player.displayName}`, e);
}
});
});
Players.playerRemoving.connect(async (player) => {
const stats = loaded.get(player);
if (!stats) return;
loaded.delete(player);
try {
await progress.set(player.userId, { v: 1, level: stats.level.value, gold: stats.gold.value });
} catch (e) {
warn(`couldn't save ${player.displayName}`, e);
}
});
import { DataStoreService, Players, task } from '@rally/sdk';
const wins = DataStoreService.getDataStore('Wins');
const inventory = DataStoreService.getDataStore('Inventory');
Players.playerAdded.connect((player) => {
task.spawn(async () => {
await wins.increment(player.userId);
await inventory.update<string[]>(player.userId, (items = []) =>
items.includes('hat') ? undefined : [...items, 'hat'],
);
});
});