Skip to content

Client

The client-side data API: bundle.Client. Read the local player's data through the same accessor tree as the server (Data.Coins.Get(), Data.Coins.Observe(fn)); those methods are documented on the Value class. Writes are local-only (optimistic UI). Server ops always win. To change data authoritatively, call a server command via Client.Request.

In edit mode (when RunService:IsRunning() is false, e.g. a storybook) the client skips the transport and initializes instantly to template defaults, so components render without a running server.

Lifecycle

.IsReady

client
Client.IsReady()  boolean

Whether the first Init snapshot has arrived (non-yielding). Before this is true, accessor reads return template defaults. Always true in edit mode.

.WaitForData

clientyields
Client.WaitForData(timeout: number?)  boolean

Yields until the first Init snapshot arrives, then returns true. Returns false if timeout seconds (default 30) pass first. It never hangs. Use it to gate startup logic; for reactive UI prefer Observe/Changed.

if Data.WaitForData() then
    showMainMenu(Data.Coins.Get())
end

Commands

.Request

clientyields
Client.Request(name: string, ...: any)  ...any

Calls a server command registered with Server.Command and returns whatever its handler returned. It yields until the reply arrives, and resolves false, "timeout" if none comes within RequestTimeout. This is the one way a client changes data authoritatively.

When Scribe refuses rather than your handler, the result is false, one Scribe.RequestReason, and Scribe.RequestFailed in a third slot. Test that third value, because a handler may return false, "timeout" of its own. A retry after a timeout runs the handler a second time, so reach for Client.RequestOnce when that is not safe. See Commands & Requests.

.RequestOnce

clientyields
Client.RequestOnce(name: string, key: string, ...: any)  ...any

Like Client.Request, but tagged with an idempotency key: the server runs the handler at most once per key and replays its original reply, byte for byte, to every repeat. Use it for anything a retry must not do twice, and generate the key once per intent rather than once per attempt.

The command must be registered Idempotent = true, and a key sent to one that is not, or a keyless Client.Request to one that is, is refused with "bad-idempotency-key". A key must be non-empty, valid UTF-8, and at most 64 bytes, and this call raises on your own thread rather than send one that is not. See Running a command at most once.

Leaderboards

.GetLeaderboard

client
Client.GetLeaderboard(name: string, limit: number?)  { LeaderboardEntry }

Types: LeaderboardEntry

The cached top-N of a replicated board (Replicate = true), streamed to clients at handshake and on change. Empty for server-only boards.

.GetMyRank

client
Client.GetMyRank(name: string)  number?

The local player's rank on a replicated board, or nil if unranked.

.OnLeaderboard

signalclient
Client.OnLeaderboard: Signal

Fires (boardName, entries) whenever a replicated board updates.

The value is a Signal, where Connect, Once and Wait are documented.

Service

.GetServiceStatus

client
Client.GetServiceStatus()  "Healthy" | "Degraded" | "Outage"

The replicated data-service health. Show players a "progress may save late" notice during a "Degraded"/"Outage".

.OnServiceStatus

signalclient
Client.OnServiceStatus: Signal

Fires with the new status whenever service health changes.

The value is a Signal, where Connect, Once and Wait are documented.

Shared Data

.GetShared

client
Client.GetShared(playerOrUserId: Player | number)  { [string]: any }?

Reads another player's Scribe.Shared roots (public info replicated to everyone), or nil if not yet received.

Never returns the local player's own roots: the server broadcasts a player's Shared data to everyone except that player, so nil there is permanent rather than pending. Read your own through the ordinary accessor.

.OnSharedChanged

signalclient
Client.OnSharedChanged: Signal

Fires (userId, sharedData) when another player's Shared data changes.

The value is a Signal, where Connect, Once and Wait are documented.

Ownership

.OnOwnershipChanged

signalclient
Client.OnOwnershipChanged: Signal

Fires (key, owned) whenever the local player's ownership of a pass or perk actually changes. Ownership already held at load is the baseline and does not fire. Use Client.ObserveOwned instead when you care about one specific key and want the current value immediately.

The value is a Signal, where Connect, Once and Wait are documented.

.Owns

clientyields
Client.Owns(key: string)  boolean

Whether the local player owns a perk or pass, from the replicated mirror. Yields until data has loaded.

.OwnsAsync

clientyields
Client.OwnsAsync(key: string, timeout: number?)  boolean

Like Client.Owns, but yields until the server has finished syncing ownership (the initial async gamepass refresh, signalled by a replicated flag). Use it so a genuinely-owned pass is never briefly reported un-owned in the window right after join. Perks/gifts are already synced at load. Falls back to the current mirror value after timeout seconds (default 10).

.ObserveOwned

client
Client.ObserveOwned(key: string, callback: (owned: boolean) -> ())  () -> ()

Calls callback with the current ownership and again whenever it changes. Returns a disconnect function. Ideal for reactively toggling "buy" buttons.

Reads

.GetSaveInfo

client
Client.GetSaveInfo()  SaveInfo

Types: SaveInfo

The owner's replicated save state ({ LastSaveAt, LastResult, Dirty, Size }) for "Saving… / Saved ✓" UI. Size is the approximate byte size of the last successful save (nil before the first).

.Raw

propertyclient
Client.Raw: Client

The same Data object without the typed accessor tree: the untyped escape hatch if the type solver ever trips on your template.

Monetization

.GetGiftCredits

client
Client.GetGiftCredits()  { [string]: number }

The local player's unassigned gift credits by product name.

.GetPurchases

client
Client.GetPurchases(filter: PurchaseFilter?)  { table }

Types: PurchaseFilter

The local player's purchase history for history UI. Only returned if the game opts into replicating purchase logs (PurchaseLog.ReplicateRobux).

PurchaseFilter is { Kind: ("Robux" | "InGame")?, Category: string?, ItemId: string?, Since: number?, Limit: number? }, exported as Scribe.PurchaseFilter.

Edit Mode

.Mock

client
Client.Mock(values: { [string]: any }?, scribeState: { Perks: { string }?, GiftCredits: { [string]: number }?, PurchaseLogs: { Robux: { any }?, InGame: { any }? }?, Leaderboards: { [string]: { LeaderboardEntry } }? }?)

Types: LeaderboardEntry

Seeds mock data for a storybook / edit mode. Errors outside edit mode. Use it to render Scribe-backed components with realistic state.

values holds template fields to seed and is deep-merged into the mirror, so you only pass the fields your component reads. scribeState seeds the monetization and leaderboard state those APIs read.

.MockCommand

client
Client.MockCommand(name: string, handler: (...any) -> ...any)

Registers a fake handler so Client.Request resolves in edit mode without a server. Errors outside edit mode.