ServerReplicator
Wraps a TableManager and replicates it to one or more clients. Every write
made through .Manager is automatically queued and sent at the end of the
current frame.
Players.PlayerAdded:Connect(function(player)
local replicator = ServerReplicator.new({
Namespace = "PlayerData",
Data = { Coins = 0 },
Targets = player,
Tags = { UserId = player.UserId },
})
replicator.Manager:Set("Coins", 100) -- replicated automatically
player.Destroying:Connect(function()
replicator:Destroy()
end)
end)
Always destroy when done
Call replicator:Destroy() when a replicator is no longer needed. The replicator
also listens to its manager's OnDestroy and tears itself down, so destroying
the manager is sufficient when the manager's lifetime drives cleanup.
Going further
-
Namespace collision safety — See
TOKENfor exclusive namespace ownership in large codebases. -
Hierarchy — Pass
Parentinstead ofReplicationTargetsto create a child replicator that inherits its ancestor's targets. UseSetParentto reparent later. -
Custom remotes — Declare signals and functions in
config.Clientto send events alongside data. SeeRegisterRemoteSignal,RegisterOrderedRemoteSignal,RegisterRemoteFunction, andRegisterOrderedRemoteFunction. -
Flush control —
Coalesced = truededuplicates redundant same-key ops per frame.ImmediateFlush = truesends this replicator's ops synchronously (before the caller's next line) instead of batching to frame end. -
Sentinels —
ServerReplicator.AllandServerReplicator.Noneare pre-built top-level replicators useful as parents.
Properties
ReplicatorCreated
StaticSignal that fires whenever a new replicator is created, regardless of token.
Id
This item is read only and cannot be modified. Read OnlyServerReplicator.Id: numberThe unique Id of this replicator.
Manager
This item is read only and cannot be modified. Read OnlyServerReplicator.Manager: TableManagerThe TableManager that this replicator is managing.
Namespace
This item is read only and cannot be modified. Read OnlyServerReplicator.Namespace: string?
The namespace string of this replicator, or nil if anonymous.
Matches any string or ReplicationToken SearchCondition when non-nil.
Token
This item is read only and cannot be modified. Read OnlyServerReplicator.Token: ReplicationToken?
The token handle for this replicator, or nil if anonymous or created with
a raw-string namespace. Present when the namespace was claimed with
ServerReplicator.TOKEN() or when a raw string resolves to the same cached
handle. Prefer .Namespace for string comparisons.
Tags
This item is read only and cannot be modified. Read OnlyServerReplicator.Tags: {[string]: any}The tags of this replicator.
ChildAdded
EventFired when a child replicator is added to this replicator.
ChildRemoved
EventFired when a child replicator is removed from this replicator.
ParentChanged
Event
Fired when this replicator's parent is changed.
Passed arguments are (newParent, oldParent).
StrictValueChecks
StaticServerReplicator.StrictValueChecks: boolean
When true, every value written through a replicator's .Manager is
recursively scanned before it is queued, and a warning naming the exact path
is emitted if it contains anything a RemoteEvent can't serialize (a function,
thread, RBXScriptSignal, or RBXScriptConnection). When false, only the
written value's own top-level type is checked (an O(1) test that still catches
the common Manager:Set(path, someFunction) mistake). Defaults to
RunService:IsStudio() -- deep scanning in Studio, cheap checks in production.
Either way the offending value is dropped in isolation and arrives as nil on
clients (see ValueGuard / BufferCodec); these checks only surface where
the bad value came from. Read-only -- use SetStrictValueChecks to change it.
TOKEN
Static
Registers name in the namespace-ownership ledger and returns a
ReplicationToken that exclusively owns that name for its lifetime.
Using a token prevents any other system from accidentally creating a
replicator with the same namespace string. Call this once per name
(usually at the top of a module), store the result, and pass it as the
Namespace (or Token) field when creating replicators.
To release the name, call ServerReplicator.TOKEN.destroy(token) after all
replicators using it have been destroyed.
You do not need a token for simple cases — a plain string Namespace
is fully supported and costs nothing extra:
-- Simple (no collision guard):
ServerReplicator.new({ Namespace = "PlayerData", ... })
-- Opt-in collision safety:
local PlayerToken = ServerReplicator.TOKEN("PlayerData")
ServerReplicator.new({ Namespace = PlayerToken, ... })
One owner per name
TOKEN("name") errors if the name is already owned by another live token
or if any raw-string replicators with that name already exist. Claim the
token at module load before creating any replicators with that name.
createRemoteEvent
StaticServerReplicator.createRemoteEvent: () → sentinel
Returns a sentinel value for use in config.Client to declare a reliable
custom signal. The sentinel is detected by ServerReplicator.new and
replaced with a real ServerCustomRemote.
local r = ServerReplicator.new({
Namespace = "MyReplicator",
ReplicationTargets = player,
Client = {
Hit = ServerReplicator.createRemoteEvent(),
GetScore = function(self, player) return 42 end,
},
})
r.Client.Hit:FireAll()
createUnreliableEvent
StaticServerReplicator.createUnreliableEvent: () → sentinel
Like createRemoteEvent but uses an UnreliableRemoteEvent so fire-and-forget
semantics apply (no ordering or delivery guarantee).
All
StaticServerReplicator.All: ServerReplicator
A top-level replicator that replicates to every current and future
player. Useful as a Parent for children that should always be visible
to everyone, but may be moved later. Do not modify it directly — only parent to it.
None
StaticServerReplicator.None: ServerReplicator
A top-level replicator that replicates to nobody. Useful as a Parent
for children that should not replicate yet and are waiting for a proper
parent. Do not modify it directly — only parent to it.
Functions
SetListenerFireMode
Static
Overrides ListenerFireMode, the scheduling used for ReplicatorCreated,
ForEach, and OnNew listeners going forward.
GetFromId
StaticReturns the replicator with the given Id, if one currently exists.
PromiseFromId
StaticReturns a promise that resolves with the replicator with the given Id, if one currently exists or is created in the future.
GetAll
StaticReturns every currently-loaded replicator matching condition (no condition = all).
ForEach
StaticServerReplicator.ForEach() → () → ()
Runs fn for every existing replicator matching condition, and again for
every future one. Returns a disconnect function that stops future invocations.
local disconnect = ServerReplicator.ForEach("PlayerData", function(replicator)
print("replicator", replicator.Id, "for", replicator.Tags.UserId)
end)
-- later:
disconnect()
PromiseFirst
StaticResolves with the first existing-or-future replicator matching condition.
GetFirst
StaticReturns the first replicator matching condition, or nil if none exists.
OnNew
StaticServerReplicator.OnNew() → () → ()
Listens for new replicators matching condition. condition accepts the
same forms as GetAll/ForEach: a namespace string, a ReplicationToken,
a tags table, or a custom predicate. Returns a disconnect function.
Prefer ForEach
OnNew only fires for replicators created after this call. Use ForEach
to also handle any already-existing matches (it runs the callback for those
immediately, then subscribes for future ones).
SetStrictValueChecks
StaticServerReplicator.SetStrictValueChecks(enabled: boolean) → ()
Overrides StrictValueChecks, toggling the recursive write-time scan of
replicated values going forward.
new
StaticServerReplicator.new() → ()Creates a new ServerReplicator.
Exactly one of Parent or ReplicationTargets must be given: a top-level
replicator is created with ReplicationTargets (pass {} to start with
none), and a child replicator is created with Parent and inherits its
top-level ancestor's targets.
Config table fields:
-
Namespace- Optional string orReplicationTokenidentifying the replicator's class. Pass a plain string for simple cases, or a token fromServerReplicator.TOKEN()for opt-in collision safety. Omit for anonymous replicators (reachable only by Id, tags, or predicate). Data- ATableManagerinstance or a raw table to replicate. Defaults to{}.-
Targets- APlayer, list ofPlayers, or"all". Only valid for top-level replicators; pass{}to start with no targets. Parent- A parentServerReplicator. Only valid for child replicators.-
Tags- Optional{ [string]: any }metadata table used for filtering inForEach/GetAll/GetFirst. -
Coalesced- If true, only the latest op per key is sent at frame end, dropping intermediate writes. Defaults to false. -
ImmediateFlush- If true, this replicator's ops are flushed to the wire synchronously (before the caller's next line) instead of at frame end, so a write lands before any externalRemoteEventfired in the same frame. Only this replicator is flushed; others keep their normal frame batching. Because each write sends immediately, N separate non-batchedSets produce N messages -- wrap bulk writes inmanager:Batch(...)to coalesce them into a single immediate send at the end of the batch. -
Client- Table declaring custom remotes. Values can beServerReplicator.createRemoteEvent(),ServerReplicator.createUnreliableEvent(), or plain functions (registered as remote functions). SeeRegisterRemoteSignal.
Namespace collision guard
If you call ServerReplicator.TOKEN("name") to claim a namespace, any
attempt to create a replicator with that name as a raw string will throw.
Pass the token object directly instead of the string. Conversely, if raw-
string replicators already exist for a name, TOKEN() will refuse to claim
it until they are all destroyed.
Conflicting Configurations
The following configurations are invalid and will throw an error:
TargetsandParentcannot both be specified.CoalescedandImmediateFlushcannot both be true.
FlushNow
StaticServerReplicator.FlushNow() → ()
Immediately flushes all pending data ops to active players without waiting
for the end-of-frame deferred flush. Use this when you need data changes to
arrive before (or alongside) an external RemoteEvent fired in the same frame.
IsTopLevel
ServerReplicator:IsTopLevel() → booleanReturns true if this replicator has no parent. Only top-level replicators can have their replication targets set directly.
GetParent
Gets the parent replicator, or nil if this is a top-level replicator.
GetChildren
Gets the immediate children of this replicator.
GetDescendants
Gets all descendants of this replicator, recursively.
FindFirstChild
ServerReplicator:FindFirstChild(recursive: boolean?--
whether to search recursively (default false)
) → ServerReplicator?Finds the first child of this replicator that matches condition, or nil if none.
PromiseFirstChild
Returns a Promise that resolves with the first child matching condition.
Resolves immediately if a matching child already exists; otherwise waits for
one to be added.
HasTags
Returns true if every key/value in tags is present on this replicator.
IsSupersetOfTags is an alias for this method.
IsSupersetOfTags
Alias for HasTags.
IsSubsetOfTags
Returns true if every key/value on this replicator is present in tags.
The inverse of HasTags: this replicator's tags must be a subset of tags.
SetTargets
TopLevelOverwrites this top-level replicator's replication targets.
replicator:SetTargets("all") -- replicate to everyone
replicator:SetTargets(player) -- single player
replicator:SetTargets({ player1, player2 }) -- explicit list
AddTarget
TopLevelAdds a player (or list of players) to this top-level replicator's targets.
RemoveTarget
TopLevelRemoves a player (or list of players) from this top-level replicator's targets.
GetTargets
Returns a list of players this top-level replicator is currently replicating to.
IsReplicatingTo
Returns whether this replicator is currently replicating to the given player.
SetParent
Re-parents this replicator under newParent, moving its entire subtree.
Cannot be called on a top-level replicator; cannot create cycles.
If the move crosses scope boundaries (different top-level ancestors), players
only in the old scope receive a Destroy, players only in the new scope
receive a fresh snapshot, and players in both receive a SetParent.
RegisterRemoteSignal
ServerReplicator:RegisterRemoteSignal(name: string) → ServerCustomRemote
Registers a reliable custom signal on this replicator. After registration,
replicator.Client[name] returns a ServerCustomRemote with Fire,
FireAll, FireExcept, FirePredicate, Connect, Once, and Wait.
-- Server
local hitSignal = replicator:RegisterRemoteSignal("Hit")
hitSignal:FireAll("headshot")
-- Client
replicator.Server.Hit:Connect(function(hitType)
print("Hit:", hitType)
end)
RegisterRemoteUnreliableSignal
ServerReplicator:RegisterRemoteUnreliableSignal(name: string) → ServerCustomRemoteLike RegisterRemoteSignal but uses an UnreliableRemoteEvent for delivery.
RegisterRemoteFunction
ServerReplicator:RegisterRemoteFunction(name: string,) → ()
Registers a client→server function. Clients invoke it via
replicator.Server[name](...) which yields until the server returns.
RegisterOrderedRemoteSignal
ServerReplicator:RegisterOrderedRemoteSignal(name: string) → ServerCustomRemote
Like RegisterRemoteSignal but delivers fires through the frame buffer,
so they arrive at the client in the same ordered sequence as data ops
enqueued in the same frame. Clients connect to it the same way:
replicator.Server[name]:Connect(...).
RegisterOrderedRemoteFunction
ServerReplicator:RegisterOrderedRemoteFunction(name: string,) → ()
Like RegisterRemoteFunction but the server's response is delivered
through the frame buffer so it arrives at the client in the same ordered
sequence as data ops. The client must call InvokeAsync(...) (returns a
Promise) instead of the blocking Invoke(...).
GetRemoteSignal
ServerReplicator:GetRemoteSignal(name: string) → ServerCustomRemoteReturns an already-registered custom signal by name.
Destroy
ServerReplicator:Destroy() → ()
Destroys the replicator and orphans any children to ServerReplicator.None.
This is synchronous and immediate; any queued ops for this replicator are
dropped, and all active players with the replicator have it destroyed on their end.