Messaging
Publish and subscribe to real-time events across every server in your game. Backed by Roblox's MessagingService with automatic JSON encoding, detached dispatch, and per-topic subscription tracking.
Cross.Publish()
Send a message to all servers (including this one) subscribed to topic. Does not yield — publishing happens in a background task.
local Cross = RoExpress.Cross
Cross.Publish("round.end", { winner = "Player1", kills = 7 })
Cross.Publish("admin.announce", "Server shutting down in 2 minutes")
Cross.Publish("ban", { userId = 12345, reason = "cheating" })
Cross.Subscribe()
Listen to a topic persistently. handler receives the payload and a context object with the sender's server ID and the time the message was sent.
Cross.Subscribe("round.end", function(data, ctx)
print(ctx.serverId, "ended a round — winner:", data.winner)
end)
Cross.Subscribe("ban", function(data, ctx)
local player = Players:GetPlayerByUserId(data.userId)
if player then
player:Kick(data.reason)
end
end)
MsgContext
| Field | Type | Description |
|---|---|---|
serverId | string | JobId of the server that published the message |
time | number | os.time() on the publishing server at send time |
Cross.Once()
Subscribe to the next message on a topic only. Automatically disconnects after the first message fires.
-- wait for the first map-vote result from any server
Cross.Once("vote.result", function(data, ctx)
SetNextMap(data.map)
end)
Cross.Unsubscribe()
Disconnect all handlers registered on a topic from this server. No-op if the topic has no active subscriptions.
Cross.Subscribe("debug.ping", function(data)
print("pong from", data.from)
end)
-- later, once debugging is done
Cross.Unsubscribe("debug.ping")
topic string counts as one subscription. Calling Subscribe on the same topic multiple times reuses the same underlying connection.Pattern: cross-server ban enforcement
-- in a shared AdminService script
local Cross = RoExpress.Cross
local Players = game:GetService("Players")
Cross.Subscribe("admin.ban", function(data)
local player = Players:GetPlayerByUserId(data.userId)
if player then
player:Kick("You have been banned: " .. (data.reason or "N/A"))
end
end)
local function BanUser(userId: number, reason: string)
-- kick locally first
local player = Players:GetPlayerByUserId(userId)
if player then player:Kick(reason) end
-- propagate to every other server
Cross.Publish("admin.ban", { userId = userId, reason = reason })
end
See also
← Cross · Shared Memory | persistent key-value across servers · Server Registry | discover live servers