Code docs

Every method, in one place

The full Luau API the plugin installs into your place: live settings, messages, event tracking, player traits and group ranks. Edit RoSignalHandlers, never RoSignal.

Copies this whole reference for ChatGPT, Claude or Cursor. Also readable at /llms.txt and /llms-full.txt.

How the pieces fit

The plugin inserts three scripts into ServerScriptService. Only two of them are yours.

ServerScriptService.RoSignal — the runtime. It talks to RoSignal, caches your settings, receives messages, and batches events. The plugin keeps it up to date, so never edit it by hand: the next setup or repair overwrites it.

ServerScriptService.RoSignalHandlers — your file, and the only one you have to touch. It is deliberately short: one Bind, one OnAnnouncement, one Track. Plugin updates never overwrite it.

RoSignalHandlers.RoSignalDemo — a starter demo UI inside your handlers script. It only exists so your very first test is visible on screen (a corner value and an announcement banner). Delete it and call your own game code instead whenever you like; the plugin never overwrites it either.

ServerScriptService.RoSignalSettings — your file too. It controls which events send a position to the Analytics map. Written once at pairing time and never overwritten.

Everything below is available after one line: local RoSignal = require(game.ServerScriptService.RoSignal). Nothing in the API errors your game — every call is wrapped, and if RoSignal is unreachable your game keeps running on the last values it had.

local RoSignal = require(game.ServerScriptService.RoSignal)

Settings (live config)

Values you change on the dashboard and read in game. A change reaches every live server in about a second — no publish, no restart.

A setting can have three values at once: the global value everyone gets, a rollout value given to a percentage of players or to a segment, and a per-player override. Get returns the global value. GetFor resolves all three for one player: override first, then rollout, then global. Inside gameplay code, use GetFor — otherwise rollouts and overrides silently do nothing.

RoSignal.Get(key: string, default: any) -> any

Read the global value of a setting, right now.

  • Reads from an in-memory cache — instant, no yield, safe in hot loops.
  • Returns `default` when the key does not exist yet or config has not arrived.
  • Ignores rollouts and player overrides. Use GetFor when a player is involved.
local damage = RoSignal.Get("Damage", 10)
RoSignal.GetFor(player: Player | number, key: string, default: any) -> any

Read the value that applies to one specific player.

  • Resolution order: player override → rollout value (if that player is in the rollout) → global value → `default`.
  • Accepts a Player instance or a raw UserId.
  • Instant, no yield.
Players.PlayerAdded:Connect(function(player)
	local damage = RoSignal.GetFor(player, "Damage", 10)
end)
RoSignal.InRollout(player: Player | number, key: string) -> boolean

True when this player is receiving a setting's rollout value.

  • Bucketing is deterministic per key and UserId, so the same player stays in or out as the percentage grows.
  • For segment-targeted rollouts it checks membership instead of the percentage.
if RoSignal.InRollout(player, "Damage") then
	-- this player is testing the new value
end
RoSignal.HasOverride(player: Player | number, key: string) -> boolean

True when this player has a per-player override on a setting.

  • Useful to skip your own balancing logic for players you are hand-tuning.
if RoSignal.HasOverride(player, "Damage") then
	print(player.Name, "is on a custom value")
end
RoSignal.OnKeyChange(key: string, fn: (newValue, oldValue) -> ())

Run a function only when one setting changes.

  • Fires whenever the value changes. If you register before the first config arrives, that first arrival counts as a change (old value nil).
  • You can register as many handlers per key as you like.
  • Handlers run in their own thread; an error in one never stops the others.
RoSignal.OnKeyChange("Damage", function(newValue, oldValue)
	print("Damage:", oldValue, "->", newValue)
end)
RoSignal.Bind(key: string, fn: (value) -> ())

Run now with the current value, then again on every change.

  • The same as OnKeyChange plus one immediate call — the usual choice for anything you apply to the world (lighting, spawn rates, multipliers).
  • The first call may receive nil if config has not arrived yet — it then runs again once the real value arrives. Handle a nil value.
local xpMultiplier = 1

RoSignal.Bind("XpMultiplier", function(value)
	xpMultiplier = value or 1
end)
RoSignal.MakeLive(module: ModuleScript | table)

Make an existing config module live without editing it.

  • Call it once from a server script (the plugin's "Make selected config live" button adds the line to RoSignalHandlers for you). Your module stays exactly as you wrote it, so autocomplete keeps working.
  • Keys that exist in the module and on RoSignal with the same type are updated in place, so every server script that reads Config.Key sees the live value. Keys only on RoSignal, or with a different type, are left alone.
  • Server only: client scripts requiring the same module keep the values written in it. Values copied into a local variable at startup don't change — read Config.Key when you need it, or use Bind.
  • Nested values use dot keys: "Desks.Tier1.Price" updates Config.Desks.Tier1.Price (the plugin push creates these automatically). Lists stay one JSON value. Pass a Folder to make every ModuleScript inside live. Frozen tables (table.freeze) can't be made live.
-- ServerScriptService.RoSignalHandlers
RoSignal.MakeLive(game:GetService("ReplicatedStorage").GameConfig)

-- Any server script, unchanged:
local Config = require(game:GetService("ReplicatedStorage").GameConfig)
humanoid:TakeDamage(Config.SwordDamage) -- live value
RoSignal.OnChange(map: { [key]: fn }) | RoSignal.OnChange(fn)

Watch several settings at once, or every setting.

  • Given a table, it registers one handler per key (same behaviour as OnKeyChange).
  • Given a single function, it runs for every setting change — use it for logging, not for gameplay.
RoSignal.OnChange({
	Damage = function(value) end,
	Speed = function(value) end,
})

RoSignal.OnChange(function(key, newValue)
	print("changed:", key, newValue)
end)

Announcements and messages

Text you push to players right now, plus custom commands when plain text is not enough.

Start with announcements: plain text, no topic, handled by OnAnnouncement. That is what the dashboard's Messages page sends by default, and it is all most games ever need.

Custom topics are the advanced path: a topic name plus structured data, handled by OnMessage. Every handler receives (data, topic, player). `player` is only set when the send targeted one player — for example an automation acting on the player who triggered it — and only on the server that player is in.

RoSignal.OnAnnouncement(fn: (text: string) -> ())

The normal way to show a message to players — plain text, no topic to choose.

  • Announcements are topic-less messages: send one from Messages, or from an automation by leaving the topic empty.
  • They also reach Studio play-tests, which MessagingService never does — Studio picks them up on its one-second settings poll, so you can test the whole thing without publishing.
  • The starter RoSignalDemo UI already pops announcements up at the top of the screen; bind your own handler to replace that.
RoSignal.OnAnnouncement(function(text)
	print("announcement:", text)
end)
RoSignal.OnMessage(topic: string, fn: (data, topic, player) -> ())

Advanced: react to a custom command topic with structured data.

  • Use this when plain text is not enough — a double-XP window, a reward, a shop rotation.
  • `data` is whatever the dashboard or automation sent — a table, a string, or nil.
  • `player` is the targeted Player instance, or nil for a broadcast.
  • Unknown topics are ignored, so you can send a topic before you write the handler.
RoSignal.OnMessage("SetWalkSpeed", function(data, topic, player)
	if not player or type(data) ~= "table" or type(data.speed) ~= "number" then
		return
	end

	local character = player.Character
	local humanoid = character and character:FindFirstChildOfClass("Humanoid")
	if humanoid then
		humanoid.WalkSpeed = data.speed
	end
end)
RoSignal.OnMessage(fn: (data, topic, player) -> ())

Catch every custom message, whatever the topic.

  • Handy for logging or for routing topics yourself. Runs in addition to topic handlers.
RoSignal.OnMessage(function(data, topic)
	print("message:", topic, data)
end)

Events

Everything RoSignal shows you in Analytics, and everything automations can trigger on, comes from Track.

Track never sends an HTTP request on the spot. The runtime counts events in memory, merges the counts across all your servers with Roblox's MemoryStore, and uploads one small summary a minute. Your counts stay exact — you just stop paying for one request per event, and your game stops waiting on the network. A thin slice of raw events is kept so you can still read real examples in the dashboard, and on server shutdown the runtime makes a best-effort attempt (up to 20 seconds) to upload pending counts.

Keep event names stable and low in number: plans limit how many distinct event names a game can use per UTC day, and a name generated per player or per item burns that limit instantly. Put the varying part in the props table instead.

RoSignal.Track(name: string, props?: table, player?: Player, options?: { map: boolean })

Report that something happened.

  • `name` is the event name shown in Analytics — a stable label like "ShopPurchase", not a per-player string.
  • `props` is a flat table of extra detail. Numbers get summed and averaged for you; strings are grouped into breakdowns. Only the first few keys are broken out (5 by default) and values are cut to 120 characters.
  • Passing `player` attributes the event to them, which is what makes per-player analytics, segments, and player-targeted automations work.
  • When a player is passed, their position is also counted into a map cell so the event shows on the Analytics map. Pass `{ map = false }` to skip that for one event, or configure it globally in RoSignalSettings.
  • Never errors and never yields: if anything is wrong the event is dropped and your game runs on.
RoSignal.Track("ShopPurchase", { item = "Sword", robux = 199 }, player)

-- a high-frequency event that would flood the map
RoSignal.Track("Heartbeat", {}, player, { map = false })

Player traits

Facts about a player that segments can filter on.

Traits describe who a player is — VIP, level, region, whatever matters to you — as opposed to events, which describe what they did. Segments built on traits then drive rollouts, player overrides, and automation actions.

RoSignal.SetTraits(player: Player | number, traits: table)

Attach or update facts about a player.

  • Traits are merged, not replaced — sending { level = 12 } leaves other traits alone.
  • Calls are batched and sent a few seconds later, so calling it repeatedly is cheap.
  • Keep the key set small and stable, the same way you would with event names.
RoSignal.SetTraits(player, { vip = true, level = 12, region = "EU" })

Group ranks

Promote a player in your linked Roblox group from inside the game.

Requires a group linked to this game in RoSignal and the Roblox group permission granted to your account. RoSignal performs the rank change with your credentials, so the group role must sit below your own.

RoSignal.SetRank(player, role: string | number | { roleId, roleName }, options?: { onlyPromote: boolean }) -> boolean, string?

Move a player into a group role.

  • Pass a role name ("Member"), a numeric role id, or a table with `roleId` / `roleName`.
  • Promote-only by default. Pass `{ onlyPromote = false }` to allow demotions.
  • Yields — call it inside task.spawn if you are on a hot path.
  • Returns `true`, or `false` plus a readable reason. It never errors.
task.spawn(function()
	local ok, err = RoSignal.SetRank(player, "Member")
	if not ok then
		warn("rank failed:", err)
	end
end)

Moderation

Bans and kicks issued in RoSignal need no code at all.

Bans go through Roblox's own restriction API, so Roblox enforces them at join time — there is nothing to write in your handlers, and a banned player cannot get in even if your game is offline from RoSignal. Kicks are delivered to the live servers by the runtime and applied for you.

For your own in-game ban rules, use Roblox's Players:BanAsync directly.

RoSignalSettings reference

Your file. Controls which events send a position to the Analytics map.

Positions are only ever read when you pass a player to Track. Turning them off saves bandwidth on events where a location means nothing.

return {
	-- Events that include a player also send that player's position.
	MapByDefault = true,

	-- Optional allow-list. When set, ONLY these events send a position,
	-- whatever MapByDefault says.
	MapEvents = nil, -- e.g. { "Death", "ShopPurchase" }

	-- Events that never send a position.
	MapExclude = {}, -- e.g. { "Heartbeat" }
}

Behaviour and guarantees

What happens when things go wrong.

No RoSignal call errors your game. Every network call is wrapped; failures are dropped quietly and retried with backoff where it makes sense.

If RoSignal is unreachable, your game keeps running on the last settings it received. When the connection returns, new values arrive and your change handlers fire as usual.

Only Get, GetFor, InRollout, and HasOverride read local state and never yield. Track and SetTraits are fire-and-forget. SetRank is the one call that yields.

On server shutdown the runtime tries for up to 20 seconds to upload pending event counts. This is best-effort: Roblox can end a server before it finishes, so the last minute is not guaranteed.

Changes to settings, announcements and messages take effect immediately — in Studio play-tests as well as in live servers. Publishing only matters when you want real players on live servers to run the inserted scripts, or after the scripts themselves change (a Repair setup).

Troubleshooting

The four things that actually go wrong.

Nothing reaches RoSignal — turn on Allow HTTP Requests in Game Settings → Security, then press Play again.

The plugin says it cannot reach rosignal.app — press Retry connection in the plugin and choose Allow, or enable it in Plugins → Manage Plugins → RoSignal → Permissions.

RoSignal or RoSignalHandlers is missing or out of date — open the plugin and press Repair setup. It reinstalls the runtime and leaves your own files alone.

You lost the pairing code, or want to rotate the key — generate a new code on the Setup page. Pairing again issues a fresh key and retires the old one, so re-run setup in Studio.