Skip to content

Change Subscription Functions ​

Subscribe to profile data changes on the server. For when the third callback argument is a path array vs ChangeInfo, see the Change listeners guide.

OnChanged ​

OnChanged(player, pathOrKey, callback) → Connection?

Subscribes to changes in a specific player's profile.

Parameters:

  • player: Player - The player to watch
  • pathOrKey: string - A top-level key (e.g. "Coins") or a dot-separated path (e.g. "Inventory.Slots")
  • callback: (newValue, oldValue, info) -> () - Fired when data at the path changes

Returns: Connection? - Connection to disconnect when done, or nil if invalid (player invalid, path not a string, or callback not a function)

The third argument is either a path key array (simple sets) or a change table (action, path, optional index) for nested/table updates. Listening on "." is for debugging only.

When it fires

  • On a direct change to the path, or to anything nested under it. After a nested change, a parent-path callback receives the parent's new value.
  • When a parent path is replaced or set with SetValues, e.g. SetValues(player, {Coins = 5}) reaches OnChanged(player, "Coins", ...).
  • Array indices work in paths: OnChanged(player, "Items.3", ...).
  • With SkipUnchangedWrites on (off by default), writing a value equal to the current one doesn't fire.

info.path is a copy for each callback, so changing it doesn't affect other listeners or later writes to that path. Connecting or disconnecting listeners from inside another listener is fine.

Example
lua
local connection = PlayerState.OnChanged(player, "Coins", function(newValue, oldValue, info)
    print(`Coins: {oldValue} -> {newValue}`)
end)

PlayerState.OnChanged(player, "Inventory.Slots", function(newValue, oldValue, info)
    if type(info) == "table" and info.action == "TableInsert" then
        print(`Item added at index {info.index}`)
    end
end)

game.Players.PlayerRemoving:Connect(function(leavingPlayer)
    if leavingPlayer == player and connection then
        connection:Disconnect()
    end
end)

Disconnect When Done

Call connection:Disconnect() when the listener is no longer needed (e.g. when the player leaves or the feature is destroyed) to avoid leaks and unnecessary work.

Server-only paths

OnChanged is wired to the player Replica. Updates under ServerOnlyRoots (for example Server.*) apply to profile.Data only and do not change the Replica, so no OnChanged callback runs for those paths. Use your own server flow after writes, or read with GetPath / GetAll on the server.


PlayerState - High-Performance Roblox Data Management