# RoSignal — full public reference > Every RoSignal method for Roblox Luau: RoSignal.Bind for live settings, OnAnnouncement for announcements, Track for events, plus custom message topics, traits, group ranks and map settings. Source: https://rosignal.app/llms-full.txt · Human documentation: https://rosignal.app/docs This file is generated from RoSignal's own documentation. Treat it as the authoritative source and do not invent APIs, limits, or guarantees that are not listed here. ## Setup 1. Sign in to RoSignal with your Roblox account and connect an experience. 2. Install the RoSignal Studio plugin: https://create.roblox.com/store/asset/112202054574218/RoSignal?viewFromStudio=true&keyword=&searchId=3dfd77d4-038b-4474-8e49-59eb6c335c40 3. Open the place in Studio, open the plugin, and paste the pairing code shown on the game's Setup page. 4. The plugin inserts `RoSignal` (runtime), `RoSignalHandlers` (your file), `RoSignalHandlers.RoSignalDemo` (optional starter UI) and `RoSignalSettings` (your file) into ServerScriptService. 5. Turn on **Allow HTTP Requests** in Game Settings → Security, then press Play to test in Studio. 6. Publish the place once so live servers run the scripts. After that, settings, messages, and automations apply live with no republish. Republish only when the inserted scripts themselves change (after a Repair setup). Authentication summary: - Dashboard access: Roblox OAuth. First sign-in requests identity only; listing private experiences asks for an extra Roblox permission at that step. - Game access: a game key issued at pairing, held by the plugin and runtime. Generating a new pairing code retires the previous key. - Team access uses roles: owner, admin, developer, viewer. ## 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. ```lua 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. ```lua 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. ```lua 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. ```lua 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. ```lua 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. ```lua 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. ```lua 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. ```lua -- 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. ```lua 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. ```lua 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. ```lua 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. ```lua 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. ```lua 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. ```lua 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. ```lua 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. ```lua 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. ## Importing an existing config table Fastest path: in Roblox Studio, select the config ModuleScript (or several, or a folder of them; the plugin sends any number of settings in batches of 200) and use the RoSignal plugin's "Push selected config to RoSignal" then "Make selected config live". The second button adds one line, `RoSignal.MakeLive(game:GetService("ReplicatedStorage").GameConfig)`, to ServerScriptService.RoSignalHandlers; the config module itself is not edited, so autocomplete and types keep working. On the server, keys present in both the module and RoSignal (same type) are updated in place, so existing `Config.Key` reads return live values. Clients keep the module's written values; values copied into locals at startup do not change; frozen tables are skipped. Nested tables are flattened into dot keys (e.g. `Desks.Tier1.Price`), which MakeLive writes back into the nested table; lists stay one JSON value. MakeLive also accepts a Folder. An agent can do the same: push via the endpoint below, then add the MakeLive line. Agents (for example with Roblox MCP) can call the same endpoint: `POST https://rosignal.app/api/public/config/import` with header `x-rosignal-key: ` and JSON body `{ "entries": [{ "key": "SwordDamage", "valueType": "number", "value": "35" }], "overwrite": false }`. Types: string, number, boolean, json. Up to 200 entries per call; keys use letters, numbers, _ and . (max 64); values max 400 characters. Existing keys are left untouched unless overwrite is true. New keys count against the plan's live-setting limit. Response: `{ imported, created, keptExisting, rejected }`. ## Automations Automations are rules built on the dashboard, not in Luau. A rule watches a signal and takes actions in the live game when it matches. - Triggers include event activity, schedules, player and segment changes, and Roblox analytics figures. - Actions include sending an announcement or custom message, changing a setting value, adding or removing players from a segment, moderation actions, and group rank changes. - Scheduled messages and automations are evaluated once a minute, then delivered immediately, so timing is accurate to within a minute. Manually sent messages are immediate. - Calling arbitrary external APIs from automations is not supported. ## Analytics - Events reported with Track are counted in memory, merged across servers, and uploaded as one summary a minute. - The dashboard shows event trends, per-event breakdowns, funnels, releases, player history, and Roblox metrics. - The map view shows where events happen, using coarse map cells derived from the player's position, never raw coordinates. - Which events contribute a position is controlled by `RoSignalSettings`. ## Security Full detail: https://rosignal.app/security - All Roblox-side code RoSignal runs in Studio or inserts into an experience is publicly auditable. - The plugin never publishes the place, never reads script source, and never touches DataStores. Map export only runs when the button is pressed. - The runtime sends: config reads, per-minute event aggregates and samples, announcement acknowledgements, traits, group rank requests, and map cells. It never sends Lua source, DataStore contents, or chat. - Authorization is enforced server-side by permission checks scoped to the account and game. - Game keys are stored hashed; secrets stay server-side. - Live actions are blocked for a game that is disconnected, and actions are recorded with the account that took them. ## Plans and limits Pricing: https://rosignal.app/pricing. Paid tier limits are not published yet. The Founding Tester plan is free and limited to the first 100 Roblox accounts, with these published limits: - Connected experiences: 20 - Events per account per day: 10,000,000 - Distinct event names per game per UTC day: 25 - Messages per day: 5,000 - Automation runs per day: 25,000 - Automations per game: 100 - Segments per game: 50 - Settings (config keys) per game: 1,000 - Player overrides per game: 50 - Team seats per game: 25 - Player history rows per game per day: 20,000 - Minute-level retention: 3 days · daily aggregate retention: 180 days ## Guides ### How to change Roblox game settings without republishing Move drop rates, multipliers and prices out of your code, then change them live across every running server. (https://rosignal.app/guides/change-roblox-game-settings-without-republishing) Most balance changes in a Roblox game are one number: an XP multiplier, a drop rate, a cooldown, a shop price. Changing that number normally means editing a script, publishing the place, and waiting for servers to cycle - which can take an hour, and every player currently online keeps the old value. The fix is to stop hardcoding the number. Read it from a value store your servers can be told about at runtime, and the change lands in seconds on every server, including the ones already running. #### 1. Read the value instead of hardcoding it In RoSignal you create a config key in the dashboard, then bind it in your game. Bind runs once with the current value and again every time you change it, so there is nothing to poll and nothing to restart. ```lua local RoSignal = require(game.ServerScriptService.RoSignal) local xpMultiplier = 1 RoSignal.Bind("XpMultiplier", function(value) xpMultiplier = value or 1 end) -- use xpMultiplier wherever you used to write the literal number local function awardXp(player, base) return base * xpMultiplier end ``` #### 2. Change it from the dashboard Edit the value in Live config and it reaches every running server through Roblox's messaging service. Cached values live inside your servers, so a network hiccup never leaves your game without a number. #### 3. Give it to some players first A value does not have to be global. Rollouts target a percentage of players, and player overrides target specific accounts - useful for testing a rebalance on yourself before everyone gets it. If the new value performs worse, set it back; there is no publish to undo. - Global value: everyone gets it immediately. - Rollout: a share of players gets the new value, the rest keep the old one. - Override: a named player or segment gets a specific value. #### What this does not cover Live config changes values, not code. A brand new mechanic, a new map, or new UI still ships as a normal Roblox update. What you gain is the ability to tune anything you decided to expose - and to undo it just as fast. ### How to see where players drop off in your Roblox game Instrument the steps that matter, then read funnels and an in-world heatmap to find the exact point players quit. (https://rosignal.app/guides/find-where-players-drop-off-roblox) Roblox's own analytics tell you that retention is down. They do not tell you that 61% of new players quit at the second tutorial gate, or that everyone who dies at the first boss never comes back. To see that, you have to name the steps yourself. #### 1. Track the moments that matter Pick the handful of moments that describe progress through your game and fire an event at each one. Keep the names stable - they become the columns of every chart you look at later. ```lua local Players = game:GetService("Players") local RoSignal = require(game.ServerScriptService.RoSignal) Players.PlayerAdded:Connect(function(player) RoSignal.Track("TutorialStarted", {}, player) end) -- Later, from wherever those moments happen (player is the one who did it): local function onBossReached(player, attempt) RoSignal.Track("BossReached", { attempt = attempt }, player) end local function onShopPurchase(player, item, robux) RoSignal.Track("ShopPurchase", { item = item, robux = robux }, player) end ``` #### 2. Build a funnel out of them A funnel lists those events in order and shows how many players reach each step. The step with the biggest fall is the one costing you players - and it is usually not the step you would have guessed. #### 3. Look at where it happens in the world Events carry the player's position when you pass a player, so the map view draws them as a heatmap over your exported geometry. A cluster of deaths in a corridor or a dead zone nobody walks through is much easier to see on the map than in a table. If an event's location means nothing, opt it out and save the bandwidth: ```lua RoSignal.Track("Heartbeat", {}, player, { map = false }) ``` #### 4. Compare before and after a change Every live config change is stamped on the timeline, so you can line up a drop-off improvement with the exact value change that caused it, instead of guessing which of the week's edits worked. ### How to send a message to every Roblox server at once Broadcast an announcement, start an event, or trigger an action on every running server - live or on a schedule. (https://rosignal.app/guides/send-messages-to-all-roblox-servers) Roblox gives you a cross-server messaging service, but on its own it only delivers a payload; you still need somewhere to send it from, a schedule, and a record of what was sent. That is the gap RoSignal fills. #### 1. Start with an announcement The simplest message is plain text with no topic at all. Your game handles it once with OnAnnouncement, and every announcement you ever send lands there. ```lua local Players = game:GetService("Players") local RoSignal = require(game.ServerScriptService.RoSignal) RoSignal.OnAnnouncement(function(text) for _, player in ipairs(Players:GetPlayers()) do -- show it however your game shows notices print(player.Name, "sees:", text) end end) ``` #### 2. Send it From the dashboard, type the announcement and send. It reaches every running server of that experience in seconds - and your Studio play-test too, so you can check it before anyone is online. Each send is logged with the account that did it, so a team of five still has one clear history. #### 3. Custom commands, when text is not enough For anything structured - a double-XP window, a reward, a shop rotation - use a topic and a data payload. Your game subscribes to the topics it cares about and decides what each one means. ```lua local doubleXp = false RoSignal.OnMessage("DoubleXp", function(data) doubleXp = type(data) == "table" and data.enabled == true end) ``` #### 4. Or schedule it Schedule the same message for a time in the future - a weekend event that starts itself at 10:00, a shop rotation, a reminder before a shutdown. You do not need to be online for it to fire. #### 5. Or let a rule send it Automations turn a condition into a message. When concurrent players cross a threshold, when an event spikes, when a schedule comes round, or when an external service calls a webhook - the rule fires and the message goes out without you touching anything. ## More - Docs: https://rosignal.app/docs - How it works: https://rosignal.app/how-it-works - FAQ: https://rosignal.app/faq - Changelog: https://rosignal.app/changelog - Status: https://rosignal.app/status