Cross → Messaging

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.

Handlers are spawned in their own task — a slow handler won't block others. The publishing server also receives its own messages.

Cross.Publish()

Cross.Publish(topic, data)

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

Cross.Subscribe(topic, handler)

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

FieldTypeDescription
serverIdstringJobId of the server that published the message
timenumberos.time() on the publishing server at send time

Cross.Once()

Cross.Once(topic, handler)

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

Cross.Unsubscribe(topic)

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 limit — MessagingService allows up to 5 active subscriptions per server. Each unique 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