Skip to content

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.

Choose a group, then follow a member link for its signature and behavior.

Lifecycle

IsReady · WaitForData · Batch · Stop

Commands

Request · RequestOnce

Leaderboards

GetLeaderboard · GetMyRank · OnLeaderboard

Service

GetServiceStatus · OnServiceStatus

Shared Data

GetShared · OnSharedChanged

Ownership

OnOwnershipChanged · Owns · OwnsAsync · ObserveOwned

Monetization

GetProductInfo · GetPrice · GetProductInfoAsync · GetPriceAsync · ObserveProductInfo · PrefetchProductInfo · RefreshProductInfo · GetProductState · PromptPurchase · ObserveProductState · GetGiftCredits · GetPurchases

Reads

GetSaveInfo · Raw · ServerStore

Edit Mode

Mock · MockCommand

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.

client yields
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
client
Client.Batch(fn: () -> ())

Runs fn as a batch: every local write inside coalesces into one Changed pass per container, the same way Server.Batch does on the server. Client writes are optimistic and never leave the client, so this changes nothing on the wire; it only stops a listener firing once per write when a UI updates several fields at once.

Frames the server sends are already applied as one batch each, so a Server.Batch arrives as one fire without any call here.

Data.Batch(function()
Data.Equipped.Set("Sword")
Data.Inventory.Sword.Qty.Decrement(1)
end)
-- Data.Inventory.Changed fires ONCE
client
Client.Stop()

Releases what this client bundle holds on the process: the transport listener, the Hello retry loop, the subscription watch behind Client.GetProductInfo, and the mirror tree’s listeners. The counterpart of Server.Stop. Product-info retries and observers stop; pending info/price reads return nil.

A game never needs it. A storybook that remounts a bundle per story, or a test suite that builds many, otherwise stacks a listener per build. Idempotent.

client yields
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.

client yields
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.

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.

client
Client.GetMyRank(name: string) → number?

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

signal client
Client.OnLeaderboard: Signal

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

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

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

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

signal client
Client.OnServiceStatus: Signal

Fires with the new status whenever service health changes.

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

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.

signal client
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.

signal client
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.

client yields
Client.Owns(key: string) → boolean

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

client yields
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).

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.

client
Client.GetProductInfo(name: string) → { [string]: any }?

Marketplace info for a declared pass or product, as this player sees it. PriceInRobux is what they are charged, after any Roblox Plus or regional discount; UserBasePriceInRobux and PriceDiscountDetails are there for a “was 100, now 80” button.

Returns nil until the first read lands, and starts that read. Poll it, or use Client.ObserveProductInfo. Never waits for the profile to load.

Client-only by necessity: a live server has no player in context and reads the base catalog price, which is not what a discounted player pays.

client
Client.GetPrice(name: string) → number?

Shorthand for the PriceInRobux of Client.GetProductInfo: the Robux this player pays. nil until the first read lands, and starts that read.

client
Client.GetProductInfoAsync(name: string, timeout: number?) → { [string]: any }?

Like Client.GetProductInfo, but yields until the read lands. Returns nil if it times out (default 10s), Marketplace refused every attempt, or Client.Stop is called.

client
Client.GetPriceAsync(name: string, timeout: number?) → number?

Shorthand for the PriceInRobux of Client.GetProductInfoAsync: the Robux this player pays. nil on timeout (default 10s), refusal, or Client.Stop.

client
Client.ObserveProductInfo(name: string, callback: ({ [string]: any }?) -> ()) → () -> ()

Calls callback with the info now (nil if it has not landed), again when it does, and again if the price changes. Returns a disconnect function. The reactive way to price a shop button.

client
Client.PrefetchProductInfo(names: { string }?)

Warms names, or every declared pass and product when omitted, as one batch. Optional: every read path fetches on demand anyway. Worth calling before opening a shop with many items, because one burst costs one request where the same reads trickling in frame by frame cost one each.

client
Client.RefreshProductInfo(name: string?)

Drops the cached info for name, or for everything when omitted, and reads it again. Scribe already does this the first time the local player’s HasRobloxSubscription flips; use this for any later move.

client
Client.GetProductState(name: string) → ProductState

Types: ProductState

The state of a declared pass or product for the local player, as one Scribe.ProductState string: "purchasable", "owned", "paid-random-restricted", "policy-pending" or "not-loaded". The same answer the server’s Server.GetProductState gives, computed here from the mirror and the local player’s own policy, so a shop can grey a button without a round trip. Raises for a name that is not declared.

client yields
Client.PromptPurchase(name: string) → boolean string? table?

Calls Server.PromptPurchase for the requesting player and returns its result. The server resolves the name, checks ownership and paid-random policy, and opens Roblox’s purchase prompt. No custom command is needed.

true means the prompt call succeeded, not that the player bought anything. Receipts and grants stay on the server. Refusals match Server.PromptPurchase; request failures additionally return Scribe.RequestFailed in the third slot. Overlapping requests are refused, and the server blocks a second prompt while one is open. Busy calls return false, "purchase prompt already open". Edit mode returns false, "edit-mode", Scribe.RequestFailed without prompting.

A timeout means the result is unknown: the server may already have opened the prompt even though its reply did not arrive. This call never retries itself. Client.Stop cannot cancel a request that has already been sent.

local opened, reason, failed = Data.PromptPurchase("CoinBag")
if not opened then
warn(reason)
end
client
Client.ObserveProductState(name: string, callback: (state: ProductState) -> ()) → () -> ()

Types: ProductState

Calls callback with the current Client.GetProductState and again whenever it changes: the profile loading, the policy landing, ownership changing. Returns a disconnect.

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

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

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.

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).

property client
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.

property client
Client.ServerStore: table

The server-owned tree as this client mirrors it, present only when the bundle declares ServerStore. Read and observe it like any other root, with no player argument; writes belong on the server.

See Server-owned state.

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.

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.