GM Market — Premium GMod & FiveM Scripts
Roblox Guide 🇺🇸 English

Roblox MessagingService: complete guide to cross-server communication (PublishAsync, SubscribeAsync, limits, use cases)

Mateo
@Mateo ·
5 views 0 replies

TL;DR

MessagingService is the native Roblox API for real-time communication between servers of the same experience. It works on a pub/sub model: one server publishes a message on a topic, and every server subscribed to that topic instantly receives it. This guide covers the API, current rate limits, and the three most common use cases: global chat, server-wide events, and cross-server trades.


How it works

  • MessagingService allows servers of the same experience to communicate in real time, typically within 1–2 seconds, using topics — developer-defined strings of 1 to 80 characters.
  • You subscribe to a topic via SubscribeAsync, and all servers subscribed to that topic are notified when it receives data via PublishAsync.
  • Delivery is best-effort and not guaranteed — architect your experience so that delivery failures are not critical.

This last point matters. MessagingService is a fire-and-forget broadcast bus, not a reliable queue. For anything requiring guaranteed delivery (e.g. inventory transfers), combine it with DataStore or MemoryStoreService.


SubscribeAsync — listening for messages

To enable cross-server messaging, you set up a topic — a customised message channel accessible from multiple servers. After creating a topic, you subscribe servers to it to receive messages.

-- ServerScript (runs on every server instance)
local MessagingService = game:GetService("MessagingService")
local Players = game:GetService("Players")

local TOPIC = "GlobalChat"

Players.PlayerAdded:Connect(function(player)
    local success, connection = pcall(function()
        return MessagingService:SubscribeAsync(TOPIC, function(message)
            local data = message.Data
            -- data.server lets you filter own-server echoes
            if data.server == game.JobId then return end
            -- Relay to local clients via RemoteEvent...
        end)
    end)
    if success then
        player.AncestryChanged:Connect(function()
            connection:Disconnect()
        end)
    end
end)

Why subscribe per player? It's a pattern from Roblox's own docs that ties cleanup to the player lifecycle. For server-level topics (events, announcements) you can call SubscribeAsync once at the top level.


PublishAsync — sending a message

PublishAsync(topic, message) sends the payload to all subscribers on that topic and yields until the backend acknowledges it.

local function broadcast(sender: string, text: string)
    local payload = { sender = sender, text = text, server = game.JobId }
    local ok, err = pcall(MessagingService.PublishAsync, MessagingService, TOPIC, payload)
    if not ok then warn("MessagingService error:", err) end
end

Always wrap in a pcall. The call yields, errors on rate-limit breach, and silently does nothing in Studio.

Does the publishing server receive its own message?

Yes. If server A and server B are both subscribed, and server A publishes, the callback fires on both A and B. Include game.JobId in your payload and return early in the callback when data.server == game.JobId to avoid processing your own messages twice.


Rate limits (post February 2024 update)

Roblox announced significantly higher MessagingService limits in February 2024 after working to enhance both the reliability and scalability of the service.

LimitMaximum
Message size1 kB
Messages sent per server / min600 + 240 × (players in that server)
Messages received per topic / min40 + 80 × (number of servers)
Messages received for entire game / min400 + 200 × (number of servers)

Key takeaways:

  • The 1 kB cap includes serialisation overhead. Send minimal payloads — IDs, not full item definitions.
  • The send budget scales with players in the publishing server. A server with 20 players gets 600 + (240 × 20) = 5,400 sends/min — plenty for chat.
  • As of May 2026, Roblox unified the OpenCloud API rate limits with the in-experience limits under a single model.
  • These limits are subject to change — always verify on the official docs.

Use case 1 — Global cross-server chat

The classic pattern listens to player chat events and sends messages to all servers, enabling cross-server communication.

game:GetService("Players").PlayerAdded:Connect(function(player)
    player.Chatted:Connect(function(msg)
        pcall(MessagingService.PublishAsync, MessagingService, "GlobalChat", {
            name   = player.Name,
            text   = msg,
            server = game.JobId,
        })
    end)
end)

Important: MessagingService cannot surface historical/old messages — it's push-only and ephemeral. For a hub lobby with chat history, pair it with MemoryStoreService (SortedMap keyed by timestamp).


Use case 2 — Server-wide events and announcements

A useful pattern is an announcement portal that pushes messages to all users across every server — announcing an upcoming event, an update, or a competition result.

For admin-triggered announcements you can either publish from an in-game command or use the Open Cloud Messaging API from an external tool. The Open Cloud API is the external equivalent of the engine MessagingService, letting you send messages to live servers from outside tools to automate operations workflows.

Topic naming tip: use specific topic names like "AdminAnnouncement" or "BossSpawn" rather than generic ones — it avoids accidental cross-feature interference.


Use case 3 — Cross-server trading

This is the most architecturally demanding pattern. General flow:

  1. Server A — Player A initiates → publish on "TradeOffer" with { from, to, items, offerId }.
  2. Server B — receives it, notifies Player B via RemoteEvent.
  3. Player B accepts → Server B publishes "TradeResponse" with { offerId, accepted = true }.
  4. Both servers update inventories via DataStore, using offerId as a deduplication key.

Critical rule: never finalise the inventory exchange through MessagingService alone. Use it to signal; commit state via DataStore. A MemoryStoreSortedMap keyed by offerId works well as a distributed lock to prevent double-processing.


MessagingService vs MemoryStoreService

FeatureMessagingServiceMemoryStoreService
Real-time push✅ Yes❌ Polling needed
Message history❌ Ephemeral✅ Persistent (TTL)
Guaranteed delivery❌ Best-effort✅ Atomic reads
Shared cross-server state❌ No✅ Yes
Best forEvents, chat, signalsQueues, locks, state

In practice they complement each other: MessagingService signals, MemoryStoreService holds truth.


Common pitfalls

  • Subscribing in a loopSubscribeAsync opens a persistent connection. Call it once per topic, not on every message received.
  • Forgetting to disconnect — always store the returned connection and call :Disconnect() on cleanup.
  • Non-serialisable values — don't send Vector3, CFrame, Instances, or mixed-key tables. Stick to strings, numbers, booleans, and plain arrays.
  • Studio testing — MessagingService does nothing in Studio. Use a RunService:IsStudio() guard and simulate locally during development.
  • Treating it as reliable — design every feature assuming a message may never arrive. Timeouts, retries, and DataStore fallbacks are your friends.

FAQ

Can I use MessagingService across different experiences? No. To publish messages across different experiences, you need the Open Cloud APIs.

Does a server receive its own published messages? Yes — filter with game.JobId in the payload.

What happens if the service is down? Messages drop silently. Design features to degrade gracefully.

Does it work on private servers? Yes — private servers are real instances and MessagingService works normally there, which makes them useful for pre-launch testing.


If you're looking for ready-made cross-server systems to study or extend, GM Market's Roblox section has community-tested scripts worth browsing. Treat any external module as a reference — audit it before dropping it into production.

Sources:

0

0 Replies

No replies yet — be the first to respond.