# Sirius Developer Suite Source: https://docs.sirius.menu/index Documentation and guides for Sirius products including Rayfield (2022), Rayfield Gen2, and the RoScripts API. Interface suite for Roblox (2022). Over 400 million executions and counting. The next generation. Redesigned from the ground up. # Button Source: https://docs.sirius.menu/rayfield-gen2/elements/buttons Run a function when the player clicks. Sentivel Endorsed by Rayfield Status pages and uptime monitoring for whatever you ship. Free to start. Take a look → A button runs its callback when clicked. That is all it does. Button elements in a window ```lua theme={null} tab:CreateButton({ name = "Reset character", callback = function() game.Players.LocalPlayer.Character:BreakJoints() end, }) ``` ## Properties The label. Hint text under the label. Optional. An icon shown beside the label. Optional. Runs on click. # Color picker Source: https://docs.sirius.menu/rayfield-gen2/elements/color-pickers Pick a colour, and optionally alpha. Sentivel Endorsed by Rayfield Status pages and uptime monitoring for whatever you ship. Free to start. Take a look → A color picker chooses a colour and, if you want it, an alpha. Drag the map and bars, or type into the fields. An open color picker with map, hue and alpha bars ```lua theme={null} tab:CreateColorPicker({ name = "Highlight", color = Color3.fromRGB(96, 205, 255), callback = function(color, alpha) print(color, alpha) end, }) ``` Color pickers are full-width and live at the tab level. Create them directly on the tab, not inside a group. ## Properties The label. Hint text under the label. Optional. An icon shown beside the label. Optional. The initial colour. A hex string is accepted. The initial alpha, from 0 to 1. The save key. Colour and alpha both persist. Optional. Skip saving. Runs with `(color, alpha)` on every change. ## Handle The current colour. The current alpha. Set the colour. Set the alpha. # Console Source: https://docs.sirius.menu/rayfield-gen2/elements/console A monospaced block for live log output or code. Sentivel Endorsed by Rayfield Status pages and uptime monitoring for whatever you ship. Free to start. Take a look → A console shows monospaced text. It holds the height you give it and scrolls inside itself, so a console that fills up never pushes the rest of the page down. ```lua theme={null} tab:CreateConsole({ name = "Example", text = [[print("Hello world")]], }) ``` Set `follow` to make it a console: new lines pin it to the bottom, and once the buffer is full the oldest line rolls off the top. ```lua theme={null} local console = tab:CreateConsole({ name = "Console", height = 130, follow = true, maxLines = 200, }) console:Append("player joined") ``` ## Properties A label above the block. Optional; without one the block is just the panel. Hint text under the block. Optional. The initial body. `code` also works. How tall the panel stands, in pixels. Floors at 48. Jump to the newest line on `Append`, the way a console does. How many lines to keep. Past this the oldest roll off the top. ## Handle Replace the whole body. Add a line to the end. Multi-line strings are split, so the line cap still counts properly. Empty it. The lines currently held, oldest first, joined by newlines. Put the body on the clipboard. Returns `false` where the executor provides no `setclipboard`. Change how tall the panel stands. # Divider Source: https://docs.sirius.menu/rayfield-gen2/elements/dividers A rule across the page, with a word in the middle or nothing at all. Sentivel Endorsed by Rayfield Status pages and uptime monitoring for whatever you ship. Free to start. Take a look → A divider breaks a page into parts without naming them the way a section does. Give it a word to sit in the middle of the rule, or drop the rule and keep only the room. A divider has no value and does not save. ```lua theme={null} tab:CreateDivider() tab:CreateDivider({ text = "or" }) tab:CreateDivider({ line = false, spacing = 20 }) -- room, no rule ``` ## Properties A word in the middle of the rule. Optional. Room above and below, in pixels. `false` draws no rule, leaving only the room. ## Handle The current word. `""` when the rule carries none. Set the word, or pass `""` to clear it and close the rule back up. It works on a divider built without one. ```lua theme={null} local divider = tab:CreateDivider() divider:Set("advanced") divider:Set("") ``` A divider is full width. Create it on a tab or inside a column group - a row warns and returns `nil`. # Dropdown Source: https://docs.sirius.menu/rayfield-gen2/elements/dropdowns Choose one option, or several. Filters as you type. Sentivel Endorsed by Rayfield Status pages and uptime monitoring for whatever you ship. Free to start. Take a look → A dropdown picks one option from a list, or several with `multiSelect`. It filters the list as the player types. ```lua theme={null} tab:CreateDropdown({ name = "Modules", multiSelect = true, options = { "Aimbot", "ESP", "Fly", "Noclip" }, value = { "ESP" }, callback = function(selected) print(selected) -- { "ESP" } end, }) ``` ## Properties The label. Hint text under the label. Optional. An icon shown beside the label. Optional. The choices. The initial selection. A string in single mode, a table in multi. Allow more than one option at a time. Shown when nothing is selected. The save key. Optional. Skip saving. Runs with the selection. A string in single mode, a `{ string }` in multi. The table is a copy, so keeping or editing it won't affect the dropdown. ## Handle The current selection. Always a table, even in single mode — the choice is the first entry. Only the callback unwraps it to a bare string in single mode. Set the selection. Replace the whole option list. Anything selected that is no longer an option is dropped, and the callback runs if that changed the selection. Add one option. It respects an open search, so it stays hidden until it matches. Remove one option. Removing a selected one clears it from the selection and runs the callback. # Input Source: https://docs.sirius.menu/rayfield-gen2/elements/inputs A text field that commits when the player is done. Sentivel Endorsed by Rayfield Status pages and uptime monitoring for whatever you ship. Free to start. Take a look → An input is a text field. It commits when the player clicks away or presses enter, not on every keystroke. Input, keybind, and color picker elements ```lua theme={null} tab:CreateInput({ name = "Max players", numeric = true, value = "16", placeholder = "Enter a number", callback = function(text) print("Committed:", text) end, }) ``` Inputs are full-width and live at the tab level. Create them directly on the tab, not inside a group. ## Properties The label. Hint text under the label. Optional. An icon shown beside the label. Optional. The initial text. Ghost text shown while the field is empty. Accept numbers only. Commits a clean number, and reverts to the previous value if the text is malformed. Empty the field when it gains focus. The save key. Optional. Skip saving. Runs with the text on commit. ## Handle The current text. Set the text. # Keybind Source: https://docs.sirius.menu/rayfield-gen2/elements/keybinds A rebindable key or mouse button. Sentivel Endorsed by Rayfield Status pages and uptime monitoring for whatever you ship. Free to start. Take a look → A keybind captures a key. Click the box, press a key or mouse button to bind it, and press Backspace to clear. Esc cancels recording and keeps the current bind. A keybind bound to LeftShift ```lua theme={null} tab:CreateKeybind({ name = "Sprint", value = Enum.KeyCode.LeftShift, callback = function(key) print("Pressed", key) end, }) ``` Keybinds are full-width and live at the tab level. Create them directly on the tab, not inside a group. ## Properties The label. Hint text under the label. Optional. An icon shown beside the label. Optional. The initial bind. A `KeyCode`, a mouse button, or its name. Hold mode. See below. Seconds to hold before a press counts, in hold mode. The save key. Optional. Skip saving. See below. Runs with the new key when the bind itself changes. ## Hold mode By default `callback(key)` fires on each press. In hold mode it fires `callback(true)` once the key is held past the threshold and `callback(false)` on release, so a quick tap is ignored. ## Handle The current bind. Set the bind. Pass `true` as the second argument to skip `onChanged`. If the key is the menu toggle key, the bind is refused with a warning and the current one is kept. # Elements overview Source: https://docs.sirius.menu/rayfield-gen2/elements/overview What every element shares, and how their handles behave. Sentivel Endorsed by Rayfield Status pages and uptime monitoring for whatever you ship. Free to start. Take a look → Elements are the controls inside a tab. Create one from a tab or group, and it returns a handle you can read from and drive later. Rayfield Gen2 elements in a window ```lua theme={null} local toggle = tab:CreateToggle({ name = "Auto Sprint" }) print(toggle.value) -- read toggle:Set(true) -- write ``` ## Shared properties Every element accepts these on top of its own. The label. Hint text under the label. An icon shown beside the label. ## Value handles Every value element (toggle, slider, dropdown, input, keybind, color picker) follows the same shape. * Read the current value from `.value`. * Write it with `Set(value)`. This fires the callback, so the change actually takes effect. * Pass `Set(value, true)` to skip the callback when you only want to move the UI. * Keybind is the exception: `Set` fires `onChanged`, never the press callback. * Dropdown's `.value` is always a table, even in single mode - see [Dropdown](/rayfield-gen2/elements/dropdowns). ```lua theme={null} local fov = tab:CreateSlider({ name = "FOV", range = { 70, 120 }, value = 90 }) fov:Set(100) -- moves the slider and fires the callback fov:Set(90, true) -- moves it silently ``` ## Flags Value elements save on their own under a key derived from their name. Pass an explicit `flag` when the name is localized, might be renamed, or could collide with another element. Set `forgetState = true` to skip saving entirely. ```lua theme={null} tab:CreateToggle({ name = "Auto Sprint", flag = "AutoSprint" }) ``` Read or write any flag through `window.Flags`, `window:Get`, and `window:Set`. See [Saving](/rayfield-gen2/saving). ## Locking Every element you can operate - button, toggle, slider, dropdown, input, keybind, color picker - can be locked. A locked element dims, stops taking input, and will not fire its callback. That last part is the one that matters: a lock that only greys the pixels is not a lock. ```lua theme={null} local farm = tab:CreateToggle({ name = "Auto Farm", description = "Collects while you idle." }) farm:Lock("Needs a premium key") -- the reason replaces the description farm:Unlock() -- and the description comes back ``` Lock it. The reason takes the place of the description while it's locked; without one, the description stays as it was. Unlock it. Whether it's currently locked. `Set` still works on a locked element, so you can move it from code while the player can't. It's user input that's blocked. ## Reordering Every element handle can move within its tab. ## The elements Run a function on click. Switch a boolean on and off. Pick a number in a range. Choose one option or several. A text field. A rebindable key. Pick a colour and alpha. A read-only number that rolls on change. How far along something is. Live log output, or a block of code. A title, a body, or both. A rule across the page, or just room. # Progress Source: https://docs.sirius.menu/rayfield-gen2/elements/progress A read-only bar showing how far along something is. Sentivel Endorsed by Rayfield Status pages and uptime monitoring for whatever you ship. Free to start. Take a look → A progress bar shows how far through a range a value sits. It takes a range the way a slider does, reads as a percentage unless you say otherwise, and is read-only - it carries no flag and saves nothing. ```lua theme={null} local download = tab:CreateProgress({ name = "Download", range = { 0, 100 }, value = 35, }) download:Set(80) ``` Set `steps` to draw the track as a row of segments instead of one continuous fill, for something that moves through stages rather than a distance. ```lua theme={null} tab:CreateProgress({ name = "Setup", steps = 5, value = 2 }) -- reads "2/5" ``` Set `indeterminate` when you don't know how long the work will take. The bar sweeps instead of filling, and the first `Set` ends the sweep. ```lua theme={null} local syncing = tab:CreateProgress({ name = "Syncing", indeterminate = true }) syncing:Set(1) -- done sweeping, now showing a value ``` ## Properties The label. Hint text under the label. Optional. An icon shown beside the label. Optional. The bounds, as `{ min, max }`. With `steps` set and no range given, it defaults to `{ 0, steps }` so the value counts stages. The initial value. Anything outside the range is clamped. Draw the track as this many segments. Needs at least 2; anything less is ignored and the bar stays continuous. A fixed readout beside the label, in place of the percentage. Build the readout yourself: `(value, min, max) -> string`. Ignored while `text` is set. Set false to hide the readout and show the track alone. Sweep instead of filling, for work of unknown length. ## Handle The current value. Set the value. Ends the sweep if the bar was indeterminate. The current value. Where the value sits in its range, 0 to 1. Move the bounds. The value comes with them, clamped to whatever the new range allows. Replace the readout, or pass nothing to go back to the formatter or the percentage. Switch between a real value and the waiting sweep. # Slider Source: https://docs.sirius.menu/rayfield-gen2/elements/sliders Pick a number in a range by dragging. Sentivel Endorsed by Rayfield Status pages and uptime monitoring for whatever you ship. Free to start. Take a look → A slider picks a number between a minimum and a maximum. The callback fires as it moves. A slider set within its range ```lua theme={null} tab:CreateSlider({ name = "Field of view", range = { 70, 120 }, increment = 1, value = 90, suffix = "°", callback = function(value) workspace.CurrentCamera.FieldOfView = value end, }) ``` ## Properties The label. Hint text under the label. Optional. An icon shown beside the label. Optional. The bounds, as `{ min, max }`. The step the slider snaps to, counted from `min`. Both bounds stay reachable even when the span isn't a whole number of steps. The initial value. A unit shown after the number. Show the track only, with no label. For dense rows. The save key. Optional. Skip saving. Runs with the new value as the slider moves. The callback takes a second argument: `(value, dragging)`. `dragging` is true from the moment the handle is grabbed until it is let go, with a final call of false once it settles, so you can show a preview while it moves and commit when it stops. ## Handle The current value. Set the value. Pass `true` as the second argument to skip the callback. # Stat Source: https://docs.sirius.menu/rayfield-gen2/elements/stats A read-only number that rolls on change and shows how far it moved. Sentivel Endorsed by Rayfield Status pages and uptime monitoring for whatever you ship. Free to start. Take a look → A stat displays a number. When it changes, the digits roll to the new value and a small indicator shows how far it moved. Stats are read-only and do not save. Stat cards showing values and their change indicators ```lua theme={null} local revenue = tab:CreateStat({ name = "Revenue", prefix = "$", value = 12400, }) revenue:Set(revenue.value + 500) ``` ## Properties The label. Hint text under the label. Optional. A compact stat has no room for it and drops it with a warning. An icon shown beside the label. Optional. The initial value. Text before the number. Text after the number. On a compact card, show the `"value"` or the `"change"`. Show a small card. Forced on inside a row. Show the delta as `"percentage"` or `"absolute"`. Measure change from the `"previous"` value or the `"initial"` one. Roll the digits rather than snap to the new value. ## Handle The current value. Set the value. Zero the change indicator. # Text Source: https://docs.sirius.menu/rayfield-gen2/elements/text A title, a body, or both, on a card of its own. Sentivel Endorsed by Rayfield Status pages and uptime monitoring for whatever you ship. Free to start. Take a look → Text is copy on a page. It carries a title, a body, or both, and the card closes up around whichever half is there. The title renders at 16, the body at 14 and a shade lighter. Text has no value and does not save. ```lua theme={null} tab:CreateText({ name = "Read this first", text = "Either half can be left out, and the card closes up around what is left.", }) ``` ## Properties The title. Optional. The body. Optional. An icon shown to the left of the title. Optional. ## Handle The current title. The current body. Set the body. Pass `""` to clear it. Set the title. Pass `""` to clear it. ```lua theme={null} local status = tab:CreateText({ name = "Status", text = "Waiting." }) status:Set("Connected.") status:SetTitle("") -- body on its own ``` Text is full width. Create it on a tab or inside a column group - a row warns and returns `nil`. # Toggle Source: https://docs.sirius.menu/rayfield-gen2/elements/toggles Switch a boolean on and off. Sentivel Endorsed by Rayfield Status pages and uptime monitoring for whatever you ship. Free to start. Take a look → A toggle flips a boolean. `CreateSwitch` is an alias for the same element. Toggle elements, one on and one off ```lua theme={null} local sprint = tab:CreateToggle({ name = "Auto Sprint", flag = "AutoSprint", value = true, callback = function(value) print("Auto Sprint:", value) end, }) sprint:Set(false) ``` ## Properties The label. Hint text under the label. Optional. An icon shown beside the label. Optional. The initial state. The save key. Optional. Skip saving. Runs with the new value on every change. ## Handle The current state. Set the state. Pass `true` as the second argument to skip the callback. # Getting started Source: https://docs.sirius.menu/rayfield-gen2/getting-started Load Rayfield Gen2 and build your first window in a few lines. Sentivel Endorsed by Rayfield Status pages and uptime monitoring for whatever you ship. Free to start. Take a look → ## Load the library Add one line at the top of your script to pull in Rayfield Gen2. ```lua Stable theme={null} local Rayfield = loadstring(game:HttpGet("https://sirius.menu/gen2"))() ``` ```lua Preview theme={null} local Rayfield = loadstring(game:HttpGet("https://sirius.menu/gen2-preview"))() ``` Stable is the release, and what your scripts should load. Preview is a rolling build from `dev` for trying what is coming next. See [Preview](/rayfield-gen2/preview). ## Build a window A window is the entry point. Create one, add a tab, and fill it with elements. ```lua theme={null} local window = Rayfield:CreateWindow({ name = "Example Hub", subtitle = "Rayfield Gen2", sidebarLayout = true, }) local tab = window:CreateTab({ name = "Home", icon = 93364949241311 }) tab:CreateButton({ name = "Say hello", callback = function() window:Notify({ title = "Hello", content = "Your first element works." }) end, }) tab:CreateToggle({ name = "Auto Sprint", callback = function(value) print("Auto Sprint:", value) end, }) ``` The first visible tab opens on its own, so there is nothing else to wire up. ## Turn on saving Pass a `configuration` table and the window remembers every value between sessions. Nothing else is required, and each value restores as its original type. ```lua theme={null} local window = Rayfield:CreateWindow({ name = "Example Hub", configuration = { autoSave = true, autoLoad = true, fileName = "ExampleHub", }, }) ``` Flags, multiple configurations, and reading values back out. ## Know when it breaks Give your window a [Sentivel](https://www.sentivel.com/?ref=sirius-docs) heartbeat, and you'll know when your script stops running before your players tell you. ```lua theme={null} local window = Rayfield:CreateWindow({ name = "Example Hub", heartbeat = "https://www.sentivel.com/api/heartbeat/", }) ``` Set up a free heartbeat in a couple of minutes. Preview channel only for now. ## Next steps Titles, tags, themes, and every window method. Organise a tab into sections, columns, and grids. Every control you can drop into a tab. Ship a build with nothing to fingerprint. # Uptime monitoring Source: https://docs.sirius.menu/rayfield-gen2/heartbeat Give your window a Sentivel heartbeat and see whether your script is running, with no loop to write. Sentivel Endorsed by Rayfield Status pages and uptime monitoring for whatever you ship. Free to start. Take a look → Preview only. This is on the [preview channel](/rayfield-gen2/preview) and is not in the stable release. It can change or be dropped before it lands there. Rayfield can report whether your script is running. Give your window a [Sentivel](https://www.sentivel.com/?ref=sirius-docs) heartbeat, and Rayfield pings it when the window opens and every five minutes while it stays open. While people are running your script, the monitor stays green. If nobody has it open, the monitor reads idle rather than down. ```lua theme={null} local window = Rayfield:CreateWindow({ name = "Example Hub", heartbeat = "https://www.sentivel.com/api/heartbeat/", }) ``` That's all you need to add. Rayfield handles the timing, runs every ping in the background, and stops when the window is unloaded. A failed ping never slows down or breaks your script. ## Set it up Sign in to [Sentivel](https://www.sentivel.com/?ref=sirius-docs) (it's free to start), open **Monitors**, click **Add a monitor**, and choose **Heartbeat**. Keep the defaults of **Every 5 minutes** for **Expected interval** and **1 minute** for **Grace period**, since Rayfield pings every five minutes. Under **When pings stop**, choose **Show it as idle**. See [When nobody is playing](#when-nobody-is-playing) for why. Sentivel gives the monitor a ping URL of the form `https://www.sentivel.com/api/heartbeat/`. Copy the whole URL. Rayfield needs this link, not the monitor's ID. Pass the URL as `heartbeat` when you create the window. ```lua theme={null} local window = Rayfield:CreateWindow({ name = "Example Hub", heartbeat = "https://www.sentivel.com/api/heartbeat/", }) ``` Add the monitor to a [Sentivel status page](https://docs.sentivel.com/guides/status-pages) and link it from your Discord. Players can check whether the script is working before they ask. ## When nobody is playing Rayfield only pings while someone has your script open, so when nobody is playing, the pings stop just as they would if the script broke. **Show it as idle** tells Sentivel what that silence means: the monitor reads **Idle** rather than down, opens no incident, records no downtime, and goes back to operational as soon as someone opens the script again. On your status page the script shows as **Idle**. Choose **Mark it down** instead only if someone almost always has the script open and you want to be alerted the moment that stops. ## What to expect * **Pings last as long as the window.** You get one ping when the window opens, then one every five minutes. They stop when you call `window:Unload()` or the window is destroyed. * **Studio is skipped.** Playtests aren't real runs, so no ping is sent there. The link is still checked, so a typo shows up while you are building. * **A bad link warns.** If the link isn't a Sentivel ping URL, Rayfield prints a warning saying what to paste instead and sends nothing. The full URL and the bare token both work, and stray spaces from a paste are fine. * **An unknown heartbeat stops.** If Sentivel keeps saying the heartbeat doesn't exist, for example because you deleted the monitor or regenerated its URL, Rayfield prints a warning and stops pinging. * **Only Sentivel.** `heartbeat` refuses any other host, so it can't be used to make other requests. * **No HTTP, no pings.** If the executor has no way to send requests, Rayfield warns once and sends nothing. [Secure mode](/rayfield-gen2/secure-mode) silences Rayfield's console output, including these warnings. Before you ship, run the script once without secure mode and check that the monitor's page says **First ping received**. The ping URL ships inside your script, so anyone who reads the script can use it. Treat the monitor as a signal that people are running your script, not as proof. If the URL is being misused, click **Regenerate** next to it on the monitor's page and ship the new one. ## Reference A window property. The monitor's ping URL, or just its token. Optional. Monitor types, thresholds, and maintenance. # Rayfield Gen2 Source: https://docs.sirius.menu/rayfield-gen2/index A full rebuild of the Rayfield Interface Suite for Roblox. Faster, more stable, and built to last. Sentivel Endorsed by Rayfield Status pages and uptime monitoring for whatever you ship. Free to start. Take a look → Rayfield Gen2 Rayfield Gen2 is a ground-up rebuild of the Rayfield Interface Suite. It builds its entire interface in code, so there is no model to load and nothing to trace. State can save to disk between sessions, the whole interface can be themed and translated at runtime, and every element is designed to feel the same on a phone as it does on a desktop. Create a window, add tabs, and fill them with elements. That is the whole model. ```lua Stable theme={null} local Rayfield = loadstring(game:HttpGet("https://sirius.menu/gen2"))() local window = Rayfield:CreateWindow({ name = "Example", subtitle = "Rayfield Gen2", sidebarLayout = true, }) local tab = window:CreateTab({ name = "Home", icon = 93364949241311 }) tab:CreateToggle({ name = "Auto Sprint", callback = function(value) print(value) end, }) ``` ```lua Preview theme={null} local Rayfield = loadstring(game:HttpGet("https://sirius.menu/gen2-preview"))() local window = Rayfield:CreateWindow({ name = "Example", subtitle = "Rayfield Gen2", sidebarLayout = true, }) local tab = window:CreateTab({ name = "Home", icon = 93364949241311 }) tab:CreateToggle({ name = "Auto Sprint", callback = function(value) print(value) end, }) ``` Stable is the release. [Preview](/rayfield-gen2/preview) is a rolling build from `dev`, for trying what is coming next. ## Highlights Built entirely in code with no marketplace model. [Secure mode](/rayfield-gen2/secure-mode) leaves nothing an anti-cheat can fingerprint. Pass a `configuration` table and every value persists and restores as its original type. No boilerplate. See [Saving](/rayfield-gen2/saving). Six built-in themes, or bring your own. Swap it live and every colour tweens across. See [Themes](/rayfield-gen2/themes). Translate every label at runtime from a table or your own resolver. See [Localization](/rayfield-gen2/localization). ## Start here Load the library and build your first window. Every window property and method. Buttons, toggles, sliders, and the rest. Restyle everything to match your script. ## Conventions Every property is optional unless marked required. Properties are camelCase, and PascalCase spellings work too, so `name` and `Name` both land. **Icons.** Anywhere an `icon` is accepted, pass an asset id number or an `rbxassetid://`, `rbxthumb://`, or `rbxasset://` string. Pass `nil`, `0`, or `""` for none. **Flags.** Value elements save on their own under a key derived from their name. Pass an explicit `flag` when you want a stable key of your own. See [Saving](/rayfield-gen2/saving). **Reordering.** Every element handle can be moved within its tab with `MoveTo(index)`, `MoveToTop()`, `MoveToBottom()`, `MoveUp()`, and `MoveDown()`. # Localization Source: https://docs.sirius.menu/rayfield-gen2/localization Translate every label at runtime. English is the source, and you only list what you change. Sentivel Endorsed by Rayfield Status pages and uptime monitoring for whatever you ship. Free to start. Take a look → Copy resolves through the localization layer, while data like numbers, keys, and hex codes is left alone. English is the source, and each English string is its own key, so a translation table only lists the strings you change. Anything you skip stays in English, and a regional id like `pt-br` beats the bare `pt`. With no `locale` given, the window follows the player's Roblox language on its own. ## Translation tables Pass tables keyed by locale id. ```lua theme={null} Rayfield:CreateWindow({ name = "Example", translations = { fr = { ["Auto Sprint"] = "Sprint auto", ["Signed in as"] = "Connecté en tant que" }, es = { ["Auto Sprint"] = "Sprint automático" }, }, }) ``` Switch languages live, with no rebuild. ```lua theme={null} window:SetLocale("fr") ``` Add a pack at runtime. ```lua theme={null} window:RegisterTranslations({ de = { ["Auto Sprint"] = "Auto-Sprint" } }) ``` ## A custom resolver To resolve copy yourself, pass a `translator`. It is a function `(source, localeId)` that returns a string, or `nil` to fall through to the tables. It is checked first, so it wins. ```lua theme={null} Rayfield:CreateWindow({ name = "Example", translator = function(source, localeId) return myTranslationService(source, localeId) -- or nil to fall back end, }) ``` Swap it at runtime with `window:SetTranslator(fn)`, then re-apply the locale to refresh the screen. # Notifications and toasts Source: https://docs.sirius.menu/rayfield-gen2/notifications Two ways to tell the player something happened. Sentivel Endorsed by Rayfield Status pages and uptime monitoring for whatever you ship. Free to start. Take a look → ## Notifications `window:Notify(props)` shows a card in the bottom-right corner. Hover to pause the timer, click to dismiss. The stack holds six at a time. Past that, the oldest retires early to make room. A notification card in the corner ```lua theme={null} window:Notify({ title = "Auto-saved", content = "Your configuration was saved.", duration = 5, }) ``` The first line. The body text. Wraps. An icon. How long it stays on screen, in seconds. Defaults to a duration based on the text length, between 3 and 9. ## Toasts `window:Toast(props)` drops a pill in from the top centre or rises from the bottom centre, newest on top. Use it for quick, low-weight confirmations. A toast pill dropping in from the top ```lua theme={null} window:Toast({ title = "Saved", icon = 125823673784681 }) window:Toast({ title = "Nova", subtitle = "joined the server", avatar = 1, }) ``` The main line. A second line. Put the subtitle above the title. An icon. A user id, shown as their headshot. A floor on the width. Caps at the pill's maximum width of 320. How long it stays on screen, in seconds. Defaults to a duration based on the text length, between 3 and 9. Where the toast appears. `"Top"` drops in from above; `"Bottom"` rises from below. # Popups Source: https://docs.sirius.menu/rayfield-gen2/popups A modal card over a dimmed backdrop. Use it for a decision or a changelog. Sentivel Endorsed by Rayfield Status pages and uptime monitoring for whatever you ship. Free to start. Take a look → `window:Popup(props)` floats a modal card over a dimmed backdrop. Give it `options` for a choice dialog, or `boxes` for a changelog. It returns a handle with `Close()`. A confirmation popup over a dimmed backdrop ## A choice dialog ```lua theme={null} window:Popup({ title = "Reset everything?", content = "This clears every saved value. You can't undo it.", options = { { text = "Cancel" }, { text = "Reset", style = "danger", callback = function() end }, }, }) ``` ## A changelog ```lua theme={null} window:Popup({ title = "What's new", subtitle = "Version 2.1", boxes = { { title = "Modal popups", description = "A new centred dialog.", icon = 93364949241311 }, { title = "Faster reveals", description = "Pages open instantly." }, }, options = { { text = "Got it", style = "primary" } }, }) ``` ## Properties The heading. A smaller line beneath the title. An icon beside the title. A body paragraph. Wraps, and scrolls inside the card when long. A list of cards, shown instead of a paragraph. Each `box` takes `title`, and optional `description` and `icon`. Footer buttons. Each `option` takes `text`, an optional `style` (`"neutral"`, `"primary"`, or `"danger"`), and an optional `callback` that runs before the popup closes. Let the player click the backdrop or press Escape to close. # Preview Source: https://docs.sirius.menu/rayfield-gen2/preview A rolling build cut from dev, for trying what is coming next. Sentivel Endorsed by Rayfield Status pages and uptime monitoring for whatever you ship. Free to start. Take a look → Preview is a second channel. It is built from the `dev` branch and republished on every green commit, so it carries work that has not reached a release yet. ```lua theme={null} local Rayfield = loadstring(game:HttpGet("https://sirius.menu/gen2-preview"))() ``` Everything on stable works the same way here. Preview also carries elements and options that stable does not have yet, and those are marked **Preview** in the sidebar and again at the top of their page. ## Not for what you ship Preview is not a supported release. It moves whenever `dev` does, and anything in it can change shape or be dropped before it reaches stable. Pages for features that only exist here say so at the top. Scripts you hand to other people should load the stable channel. ```lua theme={null} local Rayfield = loadstring(game:HttpGet("https://sirius.menu/gen2"))() ``` ## Which build you are on The bundle opens with a header comment naming the version and the commit it was built from. ```lua theme={null} -- Rayfield Gen2 v1.2.0-preview (a1b2c3d, 2026-08-20) ``` Quote that line when you report anything you hit on preview. # Saving Source: https://docs.sirius.menu/rayfield-gen2/saving State persists on its own. Read it, write it, and keep more than one configuration. Sentivel Endorsed by Rayfield Status pages and uptime monitoring for whatever you ship. Free to start. Take a look → Pass a `configuration` table to `CreateWindow` and the window persists state for you. Every value element saves unless you set `forgetState = true`, and each value restores as its original type. ```lua theme={null} local window = Rayfield:CreateWindow({ name = "Example Hub", configuration = { autoSave = true, -- save on change autoLoad = true, -- load on first open fileName = "MyConfig", customFolder = "MyGame", -- optional }, }) ``` Files write to `Rayfield/Configurations/[customFolder/].rfld`. Loading applies each value through its element, so callbacks fire and the config actually takes effect. It is not just the visuals moving. `autoLoad` runs once, when the window first opens, so reopening never overwrites changes the user has made but not saved. Elements you build after that still restore themselves, so a tab you fill in after a server fetch keeps its saved values. You can also save and load by hand. ```lua theme={null} window:Save() window:Load() ``` ## Reading and writing values Reach any saved value by its flag, without holding on to the element handle. ```lua theme={null} if window.Flags.AutoSprint then ... end -- read a live value window.Flags.AutoSprint = false -- set it, updates the UI and fires the callback window:Get("FieldOfView") -- the same read, as a method window:Set("FieldOfView", 100) -- the same write, returns false if the flag is unknown for flag, value in window.Flags do -- iterate every saved value print(flag, value) end ``` Pass an explicit `flag` to any element when its name is localized, might be renamed, or could collide with another element. A stable flag keeps saved files valid across changes. ## Multiple configurations Pass a name to keep several configurations and switch between them. Named configurations live beside the default file in the same folder. ```lua theme={null} window:Save("PvP Loadout") -- write the current state to a named configuration window:Load("PvP Loadout") -- restore it later window:ListConfigs() -- { "PvP Loadout", ... } window:DeleteConfig("PvP Loadout") ``` When saving is enabled, the settings tab gains a **Configurations** section so players can save, load, overwrite, and delete configurations with no extra code. # Secure mode Source: https://docs.sirius.menu/rayfield-gen2/secure-mode Ship a build with nothing an anti-cheat can trace back to Rayfield. Sentivel Endorsed by Rayfield Status pages and uptime monitoring for whatever you ship. Free to start. Take a look → Anti-cheats fingerprint menus by the Roblox asset ids they reference, and by probing whether a known id is loaded with `ContentProvider:GetAssetFetchStatus`. Secure mode makes sure nothing loadable can be traced back to Rayfield. Turn it on by setting a global before you load Rayfield. ```lua theme={null} getgenv().RAYFIELD_SECURE = true local Rayfield = loadstring(game:HttpGet("https://sirius.menu/gen2"))() ``` ## Why there is nothing to swap Gen2 builds its whole interface in code rather than loading a Roblox model, so there is no asset id to swap out and nothing like `RAYFIELD_ASSET_ID` to set. The only detectable surface is icon images, and secure mode handles those two ways. * **Built-in icons** (chrome, dropdown ticks, and the rest) are downloaded once and served from a local file through `getcustomasset`, so the UI still looks normal. * **Your icons**, meaning any raw asset id including Lucide-style ids, are blocked and render blank. Referencing them by id is exactly what gives a menu away. ## Keeping your own icons Cache them yourself and pass the `getcustomasset` result instead of an id. ```lua theme={null} tab:CreateButton({ name = "Alert", icon = getcustomasset("my-icons/alert.png") }) ``` The net effect: in secure mode the only assets Rayfield ever loads are local `getcustomasset` files, whose content ids are per-machine and cannot be guessed or probed for. No marketplace id and no thumbnail is ever loaded, so `GetAssetFetchStatus` on anything tied to Rayfield comes back empty. ## Avatars still work Instead of an `rbxthumb://` id, a headshot is fetched once through Roblox's thumbnail API and cached to a local file, exactly like icons. Use `avatar = userId` for this. A raw `rbxthumb://` string you pass yourself is blocked. ## The font takes the same route Rayfield's brand font is a marketplace asset like any other, so secure mode never loads it by id. The window opens in a built-in Roblox font, the real faces download and cache locally, and they swap in together once every face is on disk. A first run shows the stand-in until that finishes; later runs read from disk and swap almost immediately. If any face cannot be cached, Rayfield keeps the stand-in rather than reaching for the remote font. The trade is a plainer typeface, never a traceable one. Pass `fallbackFont` if the default stand-in clashes with your look. It takes a `Font` or an `Enum.Font`. ```lua theme={null} Rayfield:CreateWindow({ name = "My Menu", fallbackFont = Enum.Font.Gotham, }) ``` ## It also goes quiet Secure mode makes no `warn` or `print` calls, since console output is another thing anti-cheats watch. Config and callback errors that would normally log are swallowed. Develop with secure mode off, then switch it on for release. Because errors are swallowed, a mistake is much harder to spot while it is on. Secure mode needs an executor with file access (`getcustomasset`, `writefile`). If some built-in icons cannot be cached, the window shows a one-off notification so you know a few will be blank. # Tabs and groups Source: https://docs.sirius.menu/rayfield-gen2/tabs Organise a window into tabs, sections, and grids. Sentivel Endorsed by Rayfield Status pages and uptime monitoring for whatever you ship. Free to start. Take a look → ## Tabs `window:CreateTab(props)` adds a tab. The first visible tab opens automatically. A tab needs at least one of `name` or `icon`. Tabs sit in a strip under the title bar by default, or in a rail down the left with [`sidebarLayout = true`](/rayfield-gen2/windows). ```lua theme={null} local tab = window:CreateTab({ name = "Combat", icon = 93364949241311 }) ``` The tab label. An icon shown beside the label. A tab creates any element, plus sections and groups. It also has `Select(noAnimation?)`, `Deselect(noAnimation?)`, and `Remove()`. Pass `true` to select or deselect without the animation. ## Sections A section is a heading that labels the controls beneath it. `CreateSection` returns a handle that reorders like any element. ```lua theme={null} tab:CreateSection({ name = "Aiming", icon = 93364949241311 }) ``` ### Sidebar sections `window:CreateSection(props)` heads the tab rail the way `tab:CreateSection` heads an element list, grouping the tabs created after it. Same props, and the handle it returns has `Remove()`. ```lua theme={null} window:CreateSection({ name = "General" }) window:CreateTab({ name = "Home", icon = 93364949241311 }) window:CreateTab({ name = "Combat" }) window:CreateSection({ name = "Reference" }) window:CreateTab({ name = "About" }) ``` The rail only exists in the sidebar layout. Under the default top strip there is nowhere to hang a heading, so `window:CreateSection` does nothing there. ## Groups A group arranges its children along one axis. Groups nest, so a row of columns forms a grid. Set `direction` to `"row"` (the default) or `"column"`; `"horizontal"` and `"vertical"` work as aliases. A two-column grid built from nested groups ```lua theme={null} local grid = tab:CreateGroup() local left = grid:CreateGroup({ direction = "column" }) left:CreateToggle({ name = "Aimbot" }) left:CreateToggle({ name = "Triggerbot" }) local right = grid:CreateGroup({ direction = "column" }) right:CreateToggle({ name = "ESP" }) right:CreateToggle({ name = "Tracers" }) ``` Rows hold the compact elements (button, toggle, stat) along with sliders, and wrap to a new line when they fill up. Columns also take dropdowns and sections. A group offers `CreateSection` and `CreateGroup` of its own. Input, keybind, and color picker are full-width and live at the tab level. Create those directly on the tab, not inside a group. # Tags Source: https://docs.sirius.menu/rayfield-gen2/tags A small pill beside the window title. Sentivel Endorsed by Rayfield Status pages and uptime monitoring for whatever you ship. Free to start. Take a look → A tag is a small pill next to the window title, good for a status or a label. `window:CreateTag(props)` needs at least an icon or some text. ```lua theme={null} local tag = window:CreateTag({ text = "us-en", color = Color3.fromRGB(255, 175, 15), }) tag:Set({ text = "live", color = Color3.fromRGB(80, 200, 120) }) ``` ## Properties The text. `title` also works. An icon. The background. The text auto-contrasts. Sort order among the tags. ## Handle Update several properties at once. # Themes Source: https://docs.sirius.menu/rayfield-gen2/themes Six built-in themes, or bring your own. Swap it live and every colour tweens across. Sentivel Endorsed by Rayfield Status pages and uptime monitoring for whatever you ship. Free to start. Take a look → Set `theme` to a built-in name or a table, and swap it at any time with `window:ChangeTheme(theme)`. Colours and numeric values tween across, so a live change feels intentional rather than abrupt; corner radii, fonts, and gradients apply instantly. ```lua theme={null} Rayfield:CreateWindow({ name = "Example", theme = "cobalt" }) window:ChangeTheme("ember") ``` ## Built-in themes Pass any of `"default"`, `"cobalt"`, `"ember"`, `"amethyst"`, `"frost"`, or `"rose"`. ## Custom themes A custom theme only sets the keys it needs. At `CreateWindow` the rest fall back to the default. Strokes are the exception: set a fill and leave its stroke keys unset (`ElementStroke`, `ElementStrokeGradient`, `ElementStrokeHover`, `TabStroke`) and they are derived from that fill, so a colours-only theme still reads coherently. A later `ChangeTheme` with a partial table overlays the current theme instead — pass a built-in name to reset omitted keys. Any `ColorSequence` key also accepts a single `Color3`, which is widened to a flat gradient for you. ```lua theme={null} -- Recolour just the tab selector. Everything else stays default. Rayfield:CreateWindow({ theme = { TabColor = Color3.fromRGB(255, 255, 255), TabBackground = ColorSequence.new(Color3.fromRGB(24, 132, 246), Color3.fromRGB(12, 108, 230)), TabStroke = ColorSequence.new(Color3.fromRGB(96, 176, 255), Color3.fromRGB(40, 132, 246)), }, }) ``` ## Theme keys | Key | Type | | | --------------------- | --------------- | ------------------------------------------------------------ | | `CornerRoundness` | `UDim` | Window corner radius. | | `WindowColor` | `ColorSequence` | Window background. | | `ShadowColor` | `Color3` | Shadow behind the window, notifications, toasts, and popups. | | `LiveAnimation` | `boolean` | Drifts the window gradient for a livelier feel. | | `PillCornerRadius` | `UDim` | Tab pills and the collapsed icon. | | `ElementCornerRadius` | `UDim` | Element corner radius. | | `SurfaceStroke` | `Color3` | Faint rim on window, field, toast, and popup surfaces. | | Key | Type | | | ----------------------- | -------- | ----------------------- | | `TitleFont` / `Font` | `Font` | Heading and body fonts. | | `ContentColor` | `Color3` | Element text. | | `TitlingColor` | `Color3` | Title and top-bar text. | | `ElementTextHoverColor` | `Color3` | Element text on hover. | | `ActionColor` | `Color3` | Top-bar icons. | | Key | Type | | | --------------- | --------------- | ------------------- | | `TabColor` | `Color3` | Tab text and icons. | | `TabBackground` | `ColorSequence` | Tab pill fill. | | `TabStroke` | `ColorSequence` | Tab pill outline. | | Key | Type | | | -------------------------------- | --------------- | ------------------------------ | | `ElementStroke` | `Color3` | Element outline. | | `ElementGradient` | `ColorSequence` | Element fill. | | `ElementStrokeGradient` | `ColorSequence` | Sheen on gradient strokes. | | `ElementTransparency` | `number` | Element fill transparency. | | `ElementStrokeTransparency` | `number` | Outline transparency. | | `ElementStrokeHover` | `Color3` | Outline colour on hover. | | `ElementStrokeHoverTransparency` | `number` | Outline transparency on hover. | | `StatBackground` | `Color3` | Stat card surface. | | Key | Type | | | ------------------------------ | --------------- | ---------------------------------------------------------------- | | `AccentColor` / `AccentStroke` | `Color3` | Active toggle, slider glow, and popup primary button. | | `AccentGlow` | `number` | Transparency of the toggle and slider accent glow. `1` hides it. | | `SliderBackground` | `Color3` | Slider track. | | `SliderBackgroundHover` | `Color3` | Slider track on hover. | | `SliderProgress` | `ColorSequence` | Slider fill. | | `SliderStroke` | `Color3` | Slider outline. | | `SliderHandle` | `Color3` | Slider drag handle. | | `ToggleTrack` | `Color3` | Toggle switch track. | | `ToggleTrackTransparency` | `number` | Toggle track transparency. | | `ToggleKnobOff` | `Color3` | Toggle knob when off. On uses `AccentColor`. | | `ToggleKnobOffTransparency` | `number` | Toggle knob transparency when off. | | `DarkToggleOverlay` | `boolean` | Dark wash over the toggle, for light tracks. | | `DropdownHighlight` | `Color3` | Selected option highlight. | | Key | Type | | | -------------------------------------------------------------- | -------- | ------------------------------------------- | | `FieldBackground` | `Color3` | Input and keybind field fill. | | `FieldTransparency` | `number` | Field fill transparency. | | `FieldGlow` | `Color3` | Soft glow behind input and keybind fields. | | `PlaceholderColor` | `Color3` | Input and search placeholder text. | | `NeutralButton` / `NeutralButtonHover` / `NeutralButtonStroke` | `Color3` | Popup neutral button fill, hover, and edge. | | `ErrorColor` / `ErrorStrokeColor` | `Color3` | Error flash. | # Windows Source: https://docs.sirius.menu/rayfield-gen2/windows Create a window and control it at runtime. Everything else hangs off the window handle. Sentivel Endorsed by Rayfield Status pages and uptime monitoring for whatever you ship. Free to start. Take a look → `Rayfield:CreateWindow(props)` creates the window and returns its handle. Tabs, tags, notifications, popups, and theme changes all come from that handle. A Rayfield Gen2 window with tabs and elements ```lua theme={null} local window = Rayfield:CreateWindow({ name = "Example Hub", subtitle = "Rayfield Gen2", sidebarLayout = true, theme = "cobalt", }) ``` ## Properties The window title. A smaller line beneath the title. A built-in theme name (`"default"`, `"cobalt"`, `"ember"`, `"amethyst"`, `"frost"`, `"rose"`) or a theme table. See [Themes](/rayfield-gen2/themes). An icon shown beside the title. Put the tabs in a rail down the left instead of a strip across the top, and give the content its own card. The window is wider and shorter to suit it. On a narrow screen the rail keeps its icons and drops the labels. See [Tabs](/rayfield-gen2/tabs). A line under the player's name at the base of the sidebar rail. Leave it out and the name stands on its own. Sidebar layout only. Label on the collapsed pill while the window is hidden. Icon on the collapsed pill while the window is hidden. Collapse to a round icon badge rather than an icon-and-label pill. Enables saving. See [Saving](/rayfield-gen2/saving). Save on every change. Load saved state the first time the window opens. Name of the saved file. Optional folder to keep the file in. Pins the language. Leave it out to follow the player's Roblox language. See [Localization](/rayfield-gen2/localization). Language tables keyed by locale id. A custom resolver used instead of tables. A stand-in font shown in secure mode while the brand font downloads. Preview only. This is on the [preview channel](/rayfield-gen2/preview) and is not in the stable release. It can change or be dropped before it lands there. A Sentivel ping URL or token. The window pings it while it's open, so you can see whether your script is running. See [Uptime monitoring](/rayfield-gen2/heartbeat). ## Methods The window handle exposes everything you need at runtime. ### Building Add a tab. See [Tabs and groups](/rayfield-gen2/tabs). Add a pill beside the title. See [Tags](/rayfield-gen2/tags). ### Sidebar Set the line under the player's name in the sidebar rail. Pass nothing to drop it and leave the name on its own. Players can hide the block from the settings tab. The collapsed pill can be dragged anywhere on screen and stays where it's put. A press only counts as a tap if the cursor barely moved, so dragging it never opens the window by accident. ### Messages Show a notification card in the corner. See [Notifications](/rayfield-gen2/notifications). Drop a toast in from the top. See [Notifications](/rayfield-gen2/notifications). Float a modal dialog or changelog over a dimmed backdrop. See [Popups](/rayfield-gen2/popups). ### Visibility Reveal the window. Collapse the window. Toggle between shown and hidden. Collapse to the top bar. Go to a tab by its handle or name. Selects it, so the tab strip follows. Equivalent to `tab:Select()`. Destroy the interface. `true` once `Unload` has run. The handle is spent after that. ### Theme and language Restyle everything at runtime. Colours and numeric values tween across; corner radii, fonts, and gradients apply instantly. Switch every label to another language live. Swap the copy resolver. Add a language pack at runtime. ### Saving Save now. Pass a name to target a saved configuration, or leave it out for the default file. Load now. Names of the saved configurations. Delete a saved configuration. The directory and full path of the config file. Read a saved value by its flag. Also available as `window.Flags[flag]`. Write a saved value by its flag. This updates the UI and fires the callback. Returns `false` if the flag is unknown. ```lua theme={null} window:Navigate("Home") window:ChangeTheme("ember") window:ToggleHide() ``` # Anti-detection Source: https://docs.sirius.menu/rayfield/anti-detection How to use a custom asset ID to reduce Rayfield's detectability by game anti-cheats. Rayfield Gen2 is out New A ground-up rebuild — faster, undetectable by design, and actively developed. Use it for new scripts instead of Rayfield (2022). Read the Gen2 docs → Sentivel Endorsed by Rayfield Status pages and uptime monitoring for whatever you ship. Free to start. Take a look → ## How Rayfield gets detected Rayfield loads its UI through `game:GetObjects` with a known asset ID. Most anti-cheats hook this function and check the ID against a blocklist — and since Rayfield's default ID (`10804731440`) is public, it's an easy flag. The fix is simple: re-upload the UI model to your own account and point Rayfield at your copy. Now every script has its own unique asset ID that anti-cheats don't have on file. This only removes the lowest-hanging detection vector. For full protection, enable [Secure Mode](/rayfield/secure-mode) which blocks all detectable Roblox assets at runtime. *** ## Re-upload the UI model Open Roblox Studio and grab the model however you prefer: * Get it from the [Creator Store](https://create.roblox.com/store/asset/10804731440) and insert it * Search **Rayfield** in the Toolbox * Run this in the command bar: ```lua theme={null} game:GetObjects("rbxassetid://10804731440")[1].Parent = workspace ``` Right-click the model in Explorer → **Save to Roblox** → upload as a new model. Go to the asset's **Configure** page on the Roblox website and turn on **Distribute on Creator Store** — otherwise `GetObjects` won't be able to fetch it. Grab the ID from the asset URL or Asset Manager. *** ## Set your custom asset ID At the top of your script, **before** loading Rayfield: ```lua theme={null} getgenv().RAYFIELD_ASSET_ID = 123456789 -- your asset ID here ``` If you don't set this, Rayfield just uses the default ID. Still works, just detectable. *** ## Full example ```lua theme={null} getgenv().RAYFIELD_ASSET_ID = 123456789 local Rayfield = loadstring(game:HttpGet("https://sirius.menu/rayfield"))() local Window = Rayfield:CreateWindow({ Name = "My Script", -- rest of your config }) ``` *** ## Good to know * **Don't touch the model structure.** Upload it as-is. If you rename or rearrange things inside the model, Rayfield won't be able to find what it needs. * **Re-upload when Rayfield updates.** If we ship a new UI model, your old copy won't match anymore. Just re-upload the new one. * **Try to keep your asset ID to yourself.** If it ends up on a blocklist you just re-upload a new one — not a big deal, just extra work you can avoid. * **Key system UI is separate.** It has its own asset ID and only loads when the key system is enabled, so it's a lower priority. # Interactive elements Source: https://docs.sirius.menu/rayfield/elements/interactive How to create buttons, toggles, color pickers, sliders, inputs, and dropdowns. Rayfield Gen2 is out New A ground-up rebuild — faster, undetectable by design, and actively developed. Use it for new scripts instead of Rayfield (2022). Read the Gen2 docs → Sentivel Endorsed by Rayfield Status pages and uptime monitoring for whatever you ship. Free to start. Take a look → ## Button ```lua theme={null} local Button = Tab:CreateButton({ Name = "Button Example", Callback = function() -- The function that takes place when the button is pressed end, }) ``` ### Update a button ```lua theme={null} Button:Set("Button Example") ``` *** ## Toggle ```lua theme={null} local Toggle = Tab:CreateToggle({ Name = "Toggle Example", CurrentValue = false, Flag = "Toggle1", -- A flag is the identifier for the configuration file; make sure every element has a different flag if you're using configuration saving to ensure no overlaps Callback = function(Value) -- The function that takes place when the toggle is pressed -- The variable (Value) is a boolean on whether the toggle is true or false end, }) ``` ### Update a toggle ```lua theme={null} Toggle:Set(false) ``` *** ## Color picker ```lua theme={null} local ColorPicker = Tab:CreateColorPicker({ Name = "Color Picker", Color = Color3.fromRGB(255,255,255), Flag = "ColorPicker1", -- A flag is the identifier for the configuration file; make sure every element has a different flag if you're using configuration saving to ensure no overlaps Callback = function(Value) -- The function that takes place every time the color picker is moved/changed -- The variable (Value) is a Color3fromRGB value based on which color is selected end }) ``` ### Update a color picker ```lua theme={null} ColorPicker:Set(Color3.fromRGB(255,255,255)) ``` *** ## Slider ```lua theme={null} local Slider = Tab:CreateSlider({ Name = "Slider Example", Range = {0, 100}, Increment = 10, Suffix = "Bananas", CurrentValue = 10, Flag = "Slider1", -- A flag is the identifier for the configuration file; make sure every element has a different flag if you're using configuration saving to ensure no overlaps Callback = function(Value) -- The function that takes place when the slider changes -- The variable (Value) is a number that correlates to the value the slider is currently at end, }) ``` ### Update a slider ```lua theme={null} Slider:Set(10) -- The new slider integer value ``` *** ## Input ```lua theme={null} local Input = Tab:CreateInput({ Name = "Input Example", CurrentValue = "", PlaceholderText = "Input Placeholder", RemoveTextAfterFocusLost = false, Flag = "Input1", Callback = function(Text) -- The function that takes place when the input is changed -- The variable (Text) is a string for the value in the text box end, }) ``` ### Update an input ```lua theme={null} Input:Set("New Text") -- The new input text value ``` *** ## Dropdown ```lua theme={null} local Dropdown = Tab:CreateDropdown({ Name = "Dropdown Example", Options = {"Option 1", "Option 2"}, CurrentOption = {"Option 1"}, MultipleOptions = false, Flag = "Dropdown1", -- A flag is the identifier for the configuration file; make sure every element has a different flag if you're using configuration saving to ensure no overlaps Callback = function(Options) -- The function that takes place when the selected option is changed -- The variable (Options) is a table of strings for the current selected options end, }) ``` ### Refresh the option list ```lua theme={null} Dropdown:Refresh({"New Option 1","New Option 2"}) -- The new list of options ``` ### Set the selected option The option table can include multiple strings if `MultipleOptions` is `true`. ```lua theme={null} Dropdown:Set({"Option 2"}) -- "Option 2" will now be selected ``` The dropdown's callback is triggered when you call `Dropdown:Set()`. *** ## Reading element values To read the current value of an element, use `ElementName.CurrentValue`. For keybinds and dropdowns, use `KeybindName.CurrentKeybind` or `DropdownName.CurrentOption` respectively. You can also access values through the flags table: ```lua theme={null} Rayfield.Flags ``` # Keybinds Source: https://docs.sirius.menu/rayfield/elements/keybinds How to create keybind elements and update their assigned keys in Rayfield. Rayfield Gen2 is out New A ground-up rebuild — faster, undetectable by design, and actively developed. Use it for new scripts instead of Rayfield (2022). Read the Gen2 docs → Sentivel Endorsed by Rayfield Status pages and uptime monitoring for whatever you ship. Free to start. Take a look → ## Create a keybind ```lua theme={null} local Keybind = Tab:CreateKeybind({ Name = "Keybind Example", CurrentKeybind = "Q", HoldToInteract = false, Flag = "Keybind1", -- A flag is the identifier for the configuration file. Make sure every element has a different flag if you're using configuration saving to ensure no overlaps Callback = function(Keybind) -- The function that takes place when the keybind is pressed -- The variable (Keybind) is a boolean for whether the keybind is being held or not (HoldToInteract needs to be true) end, }) ``` ### Update a keybind ```lua theme={null} Keybind:Set("RightCtrl") -- Keybind (string) ``` # Text elements Source: https://docs.sirius.menu/rayfield/elements/text How to create labels, paragraphs, and dividers in Rayfield. Rayfield Gen2 is out New A ground-up rebuild — faster, undetectable by design, and actively developed. Use it for new scripts instead of Rayfield (2022). Read the Gen2 docs → Sentivel Endorsed by Rayfield Status pages and uptime monitoring for whatever you ship. Free to start. Take a look → ## Label ```lua theme={null} local Label = Tab:CreateLabel("Label Example", 4483362458, Color3.fromRGB(255, 255, 255), false) -- Title, Icon, Color, IgnoreTheme ``` ### Lucide icon support You can use a [Lucide icon](https://lucide.dev/icons/) name in place of a Roblox image ID. ```lua theme={null} local Label = Tab:CreateLabel("Label Example", "rewind") ``` Not all Lucide icons are supported. See the [full list of supported icons](https://github.com/latte-soft/lucide-roblox/tree/master/icons/compiled/48px). Credit to [Lucide](https://lucide.dev/) and [Latte Softworks](https://github.com/latte-soft/). Lucide icons and Roblox image IDs are detectable by game anti-cheats. If you need your script to be undetectable, use `getcustomasset()` instead. See [Secure Mode](/rayfield/secure-mode) for details. ### Update a label ```lua theme={null} Label:Set("Label Example", 4483362458, Color3.fromRGB(255, 255, 255), false) -- Title, Icon, Color, IgnoreTheme ``` *** ## Paragraph ```lua theme={null} local Paragraph = Tab:CreateParagraph({Title = "Paragraph Example", Content = "Paragraph Example"}) ``` ### Update a paragraph ```lua theme={null} Paragraph:Set({Title = "Paragraph Example", Content = "Paragraph Example"}) ``` *** ## Divider ```lua theme={null} local Divider = Tab:CreateDivider() ``` ### Update a divider Toggle the divider's visibility. ```lua theme={null} Divider:Set(false) -- Visible ``` # Loading the library Source: https://docs.sirius.menu/rayfield/getting-started How to load Rayfield into your script and enable configuration saving. Rayfield Gen2 is out New A ground-up rebuild — faster, undetectable by design, and actively developed. Use it for new scripts instead of Rayfield (2022). Read the Gen2 docs → Sentivel Endorsed by Rayfield Status pages and uptime monitoring for whatever you ship. Free to start. Take a look → **Share your script with the community.** Publish your script on [RoScripts](https://roscripts.io) so players can find your script. ## Load Rayfield Add the following line at the top of your script to load the Rayfield library. ```lua theme={null} local Rayfield = loadstring(game:HttpGet('https://sirius.menu/rayfield'))() ``` ## Enable configuration saving Configuration saving lets Rayfield automatically persist and restore element values across sessions. Set `ConfigurationSaving.Enabled` to `true` and provide a `FileName` when calling `CreateWindow`. Every element that supports configuration saving has a `Flag` field. Make sure each flag is unique to avoid conflicts. Place `Rayfield:LoadConfiguration()` after all your elements have been created. ```lua theme={null} Rayfield:LoadConfiguration() ``` Rayfield will now automatically save and restore your configuration on each load. ## Know when it breaks Give your window a [Sentivel](https://www.sentivel.com/?ref=sirius-docs) heartbeat, and you'll know when your script stops running before your players tell you. ```lua theme={null} local Window = Rayfield:CreateWindow({ Name = "Example Hub", Heartbeat = "https://www.sentivel.com/api/heartbeat/", }) ``` Set up a free heartbeat in a couple of minutes. # Uptime monitoring Source: https://docs.sirius.menu/rayfield/heartbeat Give your window a Sentivel heartbeat and see whether your script is running, with no loop to write. Rayfield Gen2 is out New A ground-up rebuild — faster, undetectable by design, and actively developed. Use it for new scripts instead of Rayfield (2022). Read the Gen2 docs → Sentivel Endorsed by Rayfield Status pages and uptime monitoring for whatever you ship. Free to start. Take a look → Rayfield can report whether your script is running. Give your window a [Sentivel](https://www.sentivel.com/?ref=sirius-docs) heartbeat, and Rayfield pings it when the window opens and every five minutes while it stays open. While people are running your script, the monitor stays green. If nobody has it open, the monitor reads idle rather than down. ```lua theme={null} local Window = Rayfield:CreateWindow({ Name = "Example Hub", Heartbeat = "https://www.sentivel.com/api/heartbeat/", }) ``` That's all you need to add. Rayfield handles the timing, runs every ping in the background, and stops when Rayfield is destroyed. A failed ping never slows down or breaks your script. ## Set it up Sign in to [Sentivel](https://www.sentivel.com/?ref=sirius-docs) (it's free to start), open **Monitors**, click **Add a monitor**, and choose **Heartbeat**. Keep the defaults of **Every 5 minutes** for **Expected interval** and **1 minute** for **Grace period**, since Rayfield pings every five minutes. Under **When pings stop**, choose **Show it as idle**. See [When nobody is playing](#when-nobody-is-playing) for why. Sentivel gives the monitor a ping URL of the form `https://www.sentivel.com/api/heartbeat/`. Copy the whole URL. Rayfield needs this link, not the monitor's ID. Pass the URL as `Heartbeat` in `CreateWindow`. ```lua theme={null} local Window = Rayfield:CreateWindow({ Name = "Example Hub", Heartbeat = "https://www.sentivel.com/api/heartbeat/", }) ``` Add the monitor to a [Sentivel status page](https://docs.sentivel.com/guides/status-pages) and link it from your Discord. Players can check whether the script is working before they ask. ## When nobody is playing Rayfield only pings while someone has your script open, so when nobody is playing, the pings stop just as they would if the script broke. **Show it as idle** tells Sentivel what that silence means: the monitor reads **Idle** rather than down, opens no incident, records no downtime, and goes back to operational as soon as someone opens the script again. On your status page the script shows as **Idle**. Choose **Mark it down** instead only if someone almost always has the script open and you want to be alerted the moment that stops. ## What to expect * **Pings last as long as Rayfield.** You get one ping when the window opens, after the key system if you use one, then one every five minutes. They stop when you call [`Rayfield:Destroy()`](/rayfield/windows#destroy-the-interface) or the script is run again. * **Studio is skipped.** Playtests aren't real runs, so no ping is sent there. The link is still checked, so a typo shows up while you are building. * **A bad link warns.** If the link isn't a Sentivel ping URL, Rayfield prints a warning saying what to paste instead and sends nothing. The full URL and the bare token both work, and stray spaces from a paste are fine. * **An unknown heartbeat stops.** If Sentivel keeps saying the heartbeat doesn't exist, for example because you deleted the monitor or regenerated its URL, Rayfield prints a warning and stops pinging. * **Only Sentivel.** `Heartbeat` refuses any other host, so it can't be used to make other requests. [Secure mode](/rayfield/secure-mode) silences Rayfield's console output, including these warnings. Before you ship, run the script once without secure mode and check that the monitor's page says **First ping received**. The ping URL ships inside your script, so anyone who reads the script can use it. Treat the monitor as a signal that people are running your script, not as proof. If the URL is being misused, click **Regenerate** next to it on the monitor's page and ship the new one. ## Reference A `CreateWindow` setting. The monitor's ping URL, or just its token. Optional. Monitor types, thresholds, and maintenance. # Introduction Source: https://docs.sirius.menu/rayfield/index Rayfield (2022) is a UI library for Roblox — open source, reliable, and built for production. Rayfield Gen2 is out New A ground-up rebuild — faster, undetectable by design, and actively developed. Use it for new scripts instead of Rayfield (2022). Read the Gen2 docs → Sentivel Endorsed by Rayfield Status pages and uptime monitoring for whatever you ship. Free to start. Take a look → **Share your script with the community.** Publish your script on [RoScripts](https://roscripts.io) so players can find your script. Rayfield interface Rayfield gives you a complete set of UI primitives for Roblox scripts. Windows, tabs, elements, notifications, themes — everything you need to ship a polished interface without building it yourself. ## What's included Stable across executor environments. Used in production scripts daily. Stable and consistent. Widely used across the Roblox scripting community. Full source available on [GitHub](https://github.com/SiriusSoftwareLtd/Rayfield). Contributions welcome. Key system, Discord auto-join, notifications, config saving — built in. ## Get started One line to load Rayfield into your script. Set up your first window, tabs, and sections. ## Contributing Found a bug or missing feature? Open an issue or pull request on [GitHub](https://github.com/SiriusSoftwareLtd/Rayfield), or suggest it on [Discord](https://discord.sirius.menu). # Notifications Source: https://docs.sirius.menu/rayfield/notifications How to send notifications to the user in Rayfield. Rayfield Gen2 is out New A ground-up rebuild — faster, undetectable by design, and actively developed. Use it for new scripts instead of Rayfield (2022). Read the Gen2 docs → Sentivel Endorsed by Rayfield Status pages and uptime monitoring for whatever you ship. Free to start. Take a look → ## Send a notification ```lua theme={null} Rayfield:Notify({ Title = "Notification Title", Content = "Notification Content", Duration = 6.5, Image = 4483362458, }) ``` ### Lucide icon support You can use a [Lucide icon](https://lucide.dev/icons/) name in place of a Roblox image ID. ```lua theme={null} Rayfield:Notify({ Title = "Notification Title", Content = "Notification Content", Duration = 6.5, Image = "rewind", }) ``` Not all Lucide icons are supported. See the [full list of supported icons](https://github.com/latte-soft/lucide-roblox/tree/master/icons/compiled/48px). Credit to [Lucide](https://lucide.dev/) and [Latte Softworks](https://github.com/latte-soft/). Lucide icons and Roblox image IDs are detectable by game anti-cheats. If you need your script to be undetectable, use `getcustomasset()` instead. See [Secure Mode](/rayfield/secure-mode) for details. ### Options The notification title. The notification body text. How long the notification stays visible, in seconds. Icon for the notification. Pass a Roblox image ID (number) or a Lucide icon name (string). # Secure mode Source: https://docs.sirius.menu/rayfield/secure-mode Block all detectable Roblox asset references at runtime so your script stays invisible to anti-cheats. Rayfield Gen2 is out New A ground-up rebuild — faster, undetectable by design, and actively developed. Use it for new scripts instead of Rayfield (2022). Read the Gen2 docs → Sentivel Endorsed by Rayfield Status pages and uptime monitoring for whatever you ship. Free to start. Take a look → ## Overview Starting with Build 1.74, Rayfield automatically loads its internal UI images from an external cache — no Roblox asset IDs involved. That alone makes a difference. Secure mode takes it further. It strips out every remaining reference that anti-cheats could use to identify your script: * **Icons.** Lucide names and `rbxassetid://` values are removed at runtime. * **Key system UI.** The key prompt loads a detectable model, so it's blocked entirely. * **Default model ID.** You're warned if you haven't set a [custom asset ID](/rayfield/anti-detection). The result: with secure mode on and a custom asset ID set, nothing traces back to Rayfield. *** ## Get started Set both flags before loading Rayfield: ```lua theme={null} getgenv().RAYFIELD_SECURE = true getgenv().RAYFIELD_ASSET_ID = 123456789 -- your re-uploaded model local Rayfield = loadstring(game:HttpGet("https://sirius.menu/rayfield"))() ``` If you skip `RAYFIELD_ASSET_ID`, Rayfield still works — but the default model ID remains detectable. See [Anti-Detection](/rayfield/anti-detection) for how to upload your own copy. *** ## Icons In secure mode, Lucide icon names and Roblox image IDs are blocked. They resolve to blank. Use `getcustomasset()` instead — write your images to the executor filesystem and pass the path: ```lua theme={null} -- Blocked in secure mode: Tab:CreateLabel("Hello", "alert-circle") Tab:CreateLabel("Hello", 12345678) -- Works in every mode: Tab:CreateLabel("Hello", getcustomasset("my-icons/alert.png")) ``` This applies everywhere icons are accepted: * `CreateWindow({ Icon = ... })` * `CreateTab(Name, Icon)` * `CreateLabel(Text, Icon)` * `LabelValue:Set(Text, Icon)` * `Rayfield:Notify({ Image = ... })` *** ## Key system The key system UI requires a Roblox model to render. In secure mode, that model is never loaded. * If the user already has a saved key from a previous session, everything works as expected. * If there's no saved key, the script is blocked. The key prompt can't be shown. Secure mode doesn't bypass the key system — it makes it stricter. Users without a saved key are blocked entirely. If you use `KeySystem = true`, make sure users enter their key at least once without secure mode first. *** ## Executor requirements Custom icons with `getcustomasset()` depend on the executor supporting: * `getcustomasset` * `writefile` / `isfile` / `makefolder` / `isfolder` If these aren't available, a notification appears and some icons won't render. *** ## Example A complete setup with secure mode, a custom asset ID, and custom icons: ```lua theme={null} getgenv().RAYFIELD_SECURE = true getgenv().RAYFIELD_ASSET_ID = 123456789 local Rayfield = loadstring(game:HttpGet("https://sirius.menu/rayfield"))() local Window = Rayfield:CreateWindow({ Name = "My Script", Icon = getcustomasset("my-icons/icon.png"), }) local Tab = Window:CreateTab("Main", getcustomasset("my-icons/tab.png")) Tab:CreateLabel("Status", getcustomasset("my-icons/status.png")) Rayfield:Notify({ Title = "Loaded", Content = "Script is ready", Image = getcustomasset("my-icons/notif.png"), Duration = 5, }) ``` *** ## Flags | Flag | Type | Description | | ------------------- | ------- | --------------------------------------------------- | | `RAYFIELD_SECURE` | boolean | Enables secure mode | | `RAYFIELD_ASSET_ID` | number | Custom asset ID for your copy of the Rayfield model | *** ## At a glance | Feature | Normal (1.74+) | Secure mode | | ------------------------ | -------------- | -------------- | | Internal UI images | External cache | External cache | | Lucide icons | Supported | Blocked | | Roblox image ID icons | Supported | Blocked | | `getcustomasset()` icons | Supported | Supported | | Key system | Full UI | Saved key only | | Default model ID warning | No | Yes | # Themes Source: https://docs.sirius.menu/rayfield/themes Built-in themes and custom theme support in Rayfield. Rayfield Gen2 is out New A ground-up rebuild — faster, undetectable by design, and actively developed. Use it for new scripts instead of Rayfield (2022). Read the Gen2 docs → Sentivel Endorsed by Rayfield Status pages and uptime monitoring for whatever you ship. Free to start. Take a look → ## Using a theme Pass a theme identifier to `CreateWindow`, or call `Window.ModifyTheme()` at any time. ```lua theme={null} Window.ModifyTheme('Default') ``` Or set it during window creation: ```lua theme={null} local Window = Rayfield:CreateWindow({ Theme = "Default", -- ... }) ``` *** ## Available themes | Theme name | Identifier | Preview | | ---------- | ----------- | ------------------------ | | Default | `Default` | Default | | Amber Glow | `AmberGlow` | Amber Glow | | Amethyst | `Amethyst` | Amethyst | | Bloom | `Bloom` | Bloom | | Dark Blue | `DarkBlue` | Dark Blue | | Green | `Green` | Green | | Light | `Light` | Light | | Ocean | `Ocean` | Ocean | | Serenity | `Serenity` | Serenity | *** ## Custom themes You can define your own theme as of Rayfield 1.53. Pass a theme table in place of a theme identifier string in either `ModifyTheme` or `CreateWindow`. ```lua theme={null} { TextColor = Color3.fromRGB(240, 240, 240), Background = Color3.fromRGB(25, 25, 25), Topbar = Color3.fromRGB(34, 34, 34), Shadow = Color3.fromRGB(20, 20, 20), NotificationBackground = Color3.fromRGB(20, 20, 20), NotificationActionsBackground = Color3.fromRGB(230, 230, 230), TabBackground = Color3.fromRGB(80, 80, 80), TabStroke = Color3.fromRGB(85, 85, 85), TabBackgroundSelected = Color3.fromRGB(210, 210, 210), TabTextColor = Color3.fromRGB(240, 240, 240), SelectedTabTextColor = Color3.fromRGB(50, 50, 50), ElementBackground = Color3.fromRGB(35, 35, 35), ElementBackgroundHover = Color3.fromRGB(40, 40, 40), SecondaryElementBackground = Color3.fromRGB(25, 25, 25), ElementStroke = Color3.fromRGB(50, 50, 50), SecondaryElementStroke = Color3.fromRGB(40, 40, 40), SliderBackground = Color3.fromRGB(50, 138, 220), SliderProgress = Color3.fromRGB(50, 138, 220), SliderStroke = Color3.fromRGB(58, 163, 255), ToggleBackground = Color3.fromRGB(30, 30, 30), ToggleEnabled = Color3.fromRGB(0, 146, 214), ToggleDisabled = Color3.fromRGB(100, 100, 100), ToggleEnabledStroke = Color3.fromRGB(0, 170, 255), ToggleDisabledStroke = Color3.fromRGB(125, 125, 125), ToggleEnabledOuterStroke = Color3.fromRGB(100, 100, 100), ToggleDisabledOuterStroke = Color3.fromRGB(65, 65, 65), DropdownSelected = Color3.fromRGB(40, 40, 40), DropdownUnselected = Color3.fromRGB(30, 30, 30), InputBackground = Color3.fromRGB(30, 30, 30), InputStroke = Color3.fromRGB(65, 65, 65), PlaceholderColor = Color3.fromRGB(178, 178, 178) } ``` # Windows Source: https://docs.sirius.menu/rayfield/windows How to create and manage windows, tabs, and sections in Rayfield. Rayfield Gen2 is out New A ground-up rebuild — faster, undetectable by design, and actively developed. Use it for new scripts instead of Rayfield (2022). Read the Gen2 docs → Sentivel Endorsed by Rayfield Status pages and uptime monitoring for whatever you ship. Free to start. Take a look → **Share your script with the community.** Publish your script on [RoScripts](https://roscripts.io) so players can find your script. ## Create a window `Rayfield:CreateWindow()` is the entry point for your UI. Call it once after loading the library. ```lua theme={null} local Window = Rayfield:CreateWindow({ Name = "Rayfield Example Window", Icon = 0, -- Icon in Topbar. Can use Lucide Icons (string) or Roblox Image (number). 0 to use no icon (default). LoadingTitle = "Rayfield Interface Suite", LoadingSubtitle = "by Sirius", ShowText = "Rayfield", -- for mobile users to unhide Rayfield, change if you'd like Theme = "Default", -- Check https://docs.sirius.menu/rayfield/configuration/themes ToggleUIKeybind = "K", -- The keybind to toggle the UI visibility (string like "K" or Enum.KeyCode) DisableRayfieldPrompts = false, DisableBuildWarnings = false, -- Prevents Rayfield from emitting warnings when the script has a version mismatch with the interface. -- Heartbeat = "https://www.sentivel.com/api/heartbeat/", -- Pings your Sentivel heartbeat while Rayfield is open, so you can see whether your script is running -- ScriptID = "sid_xxxxxxxxxxxx", -- Your Script ID from developer.sirius.menu — enables analytics, managed keys, and script hosting ConfigurationSaving = { Enabled = true, FolderName = nil, -- Create a custom folder for your hub/game FileName = "Big Hub" }, Discord = { Enabled = false, -- Prompt the user to join your Discord server if their executor supports it Invite = "noinvitelink", -- The Discord invite code, do not include Discord.gg/. E.g. Discord.gg/ABCD would be ABCD RememberJoins = true -- Set this to false to make them join the Discord every time they load it up }, KeySystem = false, -- Set this to true to use our key system KeySettings = { Title = "Untitled", Subtitle = "Key System", Note = "No method of obtaining the key is provided", -- Use this to tell the user how to get a key FileName = "Key", -- It is recommended to use something unique, as other scripts using Rayfield may overwrite your key file SaveKey = true, -- The user's key will be saved, but if you change the key, they will be unable to use your script GrabKeyFromSite = false, -- If this is true, set Key below to the RAW site you would like Rayfield to get the key from Key = {"Hello"} -- List of keys that the system will accept, can be RAW file links (pastebin, github, etc.) or simple strings ("hello", "key22") } }) ``` ### Options The title displayed in the window header. Icon shown in the topbar. Pass a Roblox image ID (number), a Lucide icon name (string), or `0` for no icon. Title shown on the loading screen. Subtitle shown on the loading screen. Text shown to mobile users to unhide the UI. The theme to apply. Pass a theme identifier string or a custom theme table. See [Themes](/rayfield/themes). The key that toggles UI visibility. Suppresses built-in Rayfield prompts. Prevents Rayfield from emitting warnings when there is a version mismatch between your script and the interface. A Sentivel ping URL or token. Rayfield pings it while it's open, so you can see whether your script is running. See [Uptime monitoring](/rayfield/heartbeat). Your Script ID from the [Sirius Developer Platform](https://developer.sirius.menu). Connects your script to the platform for analytics, managed keys, and hosted script delivery. Create a project at [developer.sirius.menu](https://developer.sirius.menu) to get your Script ID. Controls automatic configuration saving. Enables configuration saving. Custom folder name for saved config files. Leave `nil` to use the default. The name of the saved config file. Prompts the user to join your Discord server on supported executors. Enables the Discord join prompt. Your Discord invite code, without `discord.gg/`. For example, `discord.gg/ABCD` → `"ABCD"`. When `true`, the user is only prompted once. When `false`, they are prompted every load. The key system UI loads a detectable Roblox model. [Secure Mode](/rayfield/secure-mode) blocks the key UI entirely. Users without a saved key from a previous session won't be able to load the script. Enables the key system. Configure it with `KeySettings`. Settings for the key system. Only used when `KeySystem` is `true`. Title displayed on the key prompt. Subtitle displayed on the key prompt. Instructions for obtaining the key, shown to the user. File name used to save the key locally. Use something unique to avoid conflicts with other Rayfield scripts. When `true`, the user's key is saved so they don't need to re-enter it. Note: changing the key will invalidate saved keys. When `true`, Rayfield fetches the key from the URL specified in `Key`. A list of accepted keys (strings), or RAW file URLs (Pastebin, GitHub, etc.) when `GrabKeyFromSite` is `true`. Looking for server-validated keys with ad-gated delivery? The [Sirius Developer Platform](https://developer.sirius.menu) offers a managed key system with user-hash locking, per-key watermarks, and ad provider integration — available on all plans including Free. *** ## Create a tab Tabs are the top-level sections inside a window. Each tab can hold its own elements. ```lua theme={null} local Tab = Window:CreateTab("Tab Example", 4483362458) -- Title, Image ``` ### Lucide icon support You can use a [Lucide icon](https://lucide.dev/icons/) name in place of a Roblox image ID. ```lua theme={null} local Tab = Window:CreateTab("Tab Example", "rewind") ``` Not all Lucide icons are supported. See the [full list of supported icons](https://github.com/latte-soft/lucide-roblox/tree/master/icons/compiled/48px). Credit to [Lucide](https://lucide.dev/) and [Latte Softworks](https://github.com/latte-soft/). Lucide icons and Roblox image IDs are detectable by game anti-cheats. If you need your script to be undetectable, use `getcustomasset()` instead. See [Secure Mode](/rayfield/secure-mode) for details. *** ## Create a section Sections add a labelled group header inside a tab. ```lua theme={null} local Section = Tab:CreateSection("Section Example") ``` ### Update a section ```lua theme={null} Section:Set("Section Example") ``` *** ## Create a divider Dividers add a horizontal rule inside a tab to visually separate content. ```lua theme={null} local Divider = Tab:CreateDivider() ``` ### Update a divider ```lua theme={null} Divider:Set(false) -- Whether the divider's visibility is to be set to true or false. ``` *** ## Visibility ### Set visibility ```lua theme={null} Rayfield:SetVisibility(false) ``` ### Get visibility ```lua theme={null} Rayfield:IsVisible() ``` *** ## Destroy the interface ```lua theme={null} Rayfield:Destroy() ``` # Authentication Source: https://docs.sirius.menu/scripts-api/authentication The RoScripts API is open. An optional API key raises your rate limits and page-size caps. The RoScripts API is **open by default** — you can call every endpoint anonymously, rate-limited by IP. An API key is optional and does one thing: it raises your limits. No key. Limited by IP. `max` page size capped at **20**. Perfect for trying things out and light traffic. Higher per-minute and per-day limits. `max` page size up to **100**. Best for hubs and production traffic. ## Getting a key Head to your [account settings](https://roscripts.io/account) on roscripts.io. You'll need a verified email. Open the **API keys** section and click **Create key**. Give it a name you'll recognise (e.g. "my-hub-prod"). The full key is shown **exactly once**, at creation. Store it somewhere safe — we only keep a hash, so we can't show it again. ## Using a key Send it as a Bearer token in the `Authorization` header: ```bash cURL theme={null} curl "https://api.roscripts.io/v1/scripts?max=100" \ -H "Authorization: Bearer rk_live_xxxxxxxxxxxxxxxx" ``` ```js JavaScript theme={null} const res = await fetch("https://api.roscripts.io/v1/scripts?max=100", { headers: { Authorization: "Bearer rk_live_xxxxxxxxxxxxxxxx" }, }); ``` ```lua Luau theme={null} local res = game:HttpGet("https://api.roscripts.io/v1/scripts?max=100") -- Most executors can't set custom headers on HttpGet; for keyed requests -- use request()/http_request if your executor exposes it: local r = request({ Url = "https://api.roscripts.io/v1/scripts?max=100", Method = "GET", Headers = { Authorization = "Bearer rk_live_xxxxxxxxxxxxxxxx" }, }) ``` Treat your key like a password. Don't ship it in client-side JavaScript or commit it to a public repo. If a key leaks, revoke it from your [account settings](https://roscripts.io/account) and create a new one — revocation takes effect within seconds. ## How keys are stored We store only a SHA-256 hash of your key, never the raw value — the same way password-reset tokens are handled across Sirius. A database leak can't be turned into working keys. The trade-off: we genuinely cannot recover a lost key, so keep it safe and rotate if in doubt. ## Tiers | Tier | How to get it | Page-size cap (`max`) | | ------------- | ------------------------------------------------------------------ | --------------------- | | **Anonymous** | Nothing — just call the API | 20 | | **Free key** | Create one in account settings | 100 | | **Partner** | [Contact us](https://discord.gg/KQ4kwzA87s) for high-volume access | 100 | See [Rate limits](/scripts-api/rate-limits) for the exact request budgets per tier. An invalid or revoked key never hard-fails your request — the API simply treats you as anonymous and applies anonymous limits. So a rotated key degrades gracefully instead of returning `401`. # Best practices Source: https://docs.sirius.menu/scripts-api/best-practices Build a fast, resilient integration: cache aggressively, sync instead of scraping, execute via loadstring, and fail gracefully. Follow these and your integration will be fast, cheap, and resilient — and you'll almost never see a `429`. ## Cache everything The single biggest win. Responses are served from Cloudflare's edge and carry `Cache-Control` + `ETag`. You should layer your own cache on top. Trending changes slowly (5 min), detail pages medium (1 min), lists fast (30 s). Don't re-fetch inside those windows. Store the `ETag` and send `If-None-Match`. A `304` is free — it doesn't count against your rate limit. ```js theme={null} const cache = new Map(); // url -> { etag, body } async function getCached(url) { const prev = cache.get(url); const res = await fetch(url, prev ? { headers: { "If-None-Match": prev.etag } } : {}); if (res.status === 304) return prev.body; const body = await res.json(); cache.set(url, { etag: res.headers.get("etag"), body }); return body; } ``` ## Sync, don't re-scrape If you maintain a local copy of the catalogue, **never** re-page through everything on a schedule. Pull only what changed: Page through `GET /v1/scripts` with `sortBy=updatedAt&order=asc` using `cursor`, storing each script and the max `updatedAt` you see. On a timer, call `GET /v1/scripts?updatedSince=&sortBy=updatedAt&order=asc` and upsert the results. Advance `lastSeen` to the new max `updatedAt`. Need to refresh a known set (e.g. the scripts a user saved)? Use [`POST /v1/scripts/batch`](/scripts-api/endpoints/batch) instead of one request per script. ## Execute with `loadstring` Every script returns a `loadstring` URL — a stable, separately-cached execution endpoint at `script.roscripts.io`. Use it directly rather than fetching raw source and reconstructing the loader: ```lua theme={null} loadstring(game:HttpGet(script.loadstring))() ``` Only call `loadstring` on scripts the user explicitly chose to run, and surface the script's `key` and `patched` flags first. Never auto-execute search results. ## Filter server-side Push filters into the query instead of fetching broadly and filtering on the client — it's faster, cheaper, and paginates correctly: ```bash theme={null} # Good — let the API do the work curl "https://api.roscripts.io/v1/scripts?placeId=2753915549&key=0&verified=1&sortBy=views" ``` Combine `mode`, `key`, `universal`, `verified`, `patched`, `placeId`, `game`, `tag`, `ui`, `owner`, `minViews`, and `hasThumbnail` freely. See the [list endpoint](/scripts-api/endpoints/list). ## Trim payloads with `fields` Only need a few fields for a list view? Ask for them: ```bash theme={null} curl "https://api.roscripts.io/v1/scripts?fields=id,title,slug,image,loadstring&max=50" ``` `id` is always included so you can correlate results. ## Show quality signals The catalogue carries trust signals — surface them so users make good choices: | Field | Show it as | | ----------------------------- | ---------------------------------------- | | `verified` | A verified-creator badge | | `key` | A "key system" warning before execution | | `patched` | A "may be patched" flag | | `scriptType: "paid"` | A price/marketplace link (no raw source) | | `score`, `likeCount`, `views` | Sort + social proof | ## Fail gracefully Switch on the stable `error.code`, not the human message. See [Errors](/scripts-api/errors). Honour `Retry-After`. Retry a handful of times with delay, then surface a friendly message. ## Set a descriptive User-Agent Identify your app (e.g. `MyHub/2.1 (+https://myhub.example)`). It helps us help you if something looks off, and keeps you clear of generic-bot heuristics. ## Don't The catalogue isn't real-time. Polling `trending` every second just burns your budget and serves you cached data anyway. Poll on the order of minutes. The `/raw` endpoint is rate-limited harder and is for fetching the occasional script a user opened — not for mirroring source en masse. Keys belong on a server. If you must call from a client, do it anonymously or proxy through your backend. We add fields over time. Parse defensively, ignore unknown fields, and never index by position. # Batch resolve Source: https://docs.sirius.menu/scripts-api/endpoints/batch POST https://api.roscripts.io/v1/scripts/batch Resolve many scripts in a single request. Resolve up to **20** scripts (or **100** with an API key) in one request by id, slug, or shortId. Ideal for refreshing a known set — a user's saved list, a curated hub, a local cache. ```bash theme={null} curl -X POST "https://api.roscripts.io/v1/scripts/batch" \ -H "Content-Type: application/json" \ -d '{ "refs": ["blox-fruits-hub", "lst_9f2c…", "Ab3xK9q"] }' ``` ## Body Array of script ids, slugs, or shortIds. Max 20 anonymous, 100 with a key. Duplicates are de-duplicated. ## Response The scripts that resolved (live only). Any refs that didn't match a live script. Number of scripts returned. ```json theme={null} { "result": { "scripts": [ { "id": "lst_…", "slug": "blox-fruits-hub", "loadstring": "https://script.roscripts.io/…" } ], "missing": ["Ab3xK9q"], "count": 1 } } ``` Batch is a `POST`, so it isn't shared-cached at the edge. For browsing, prefer the cacheable `GET` list/search endpoints; reserve batch for resolving a specific known set. # Games Source: https://docs.sirius.menu/scripts-api/endpoints/games Browse the games that have scripts, and list scripts per game. ## Game directory ``` GET https://api.roscripts.io/v1/games ``` Lists every game that has at least one live script, ordered by 30-day view volume. Up to 200. ```json theme={null} { "result": { "games": [ { "placeId": 2753915549, "name": "Blox Fruits", "image": "https://…", "scriptCount": 64, "views": 182304 } ], "page": 1, "max": 50, "total": 1208, "totalPages": 25, "nextPage": 2 } } ``` *** ## Game info ``` GET https://api.roscripts.io/v1/games/{placeId} ``` Roblox place id. ```json theme={null} { "result": { "game": { "placeId": 2753915549, "name": "Blox Fruits", "image": "https://…", "scriptCount": 64, "views": 182304 } } } ``` Returns `404` if no live scripts exist for that game. *** ## Scripts for a game ``` GET https://api.roscripts.io/v1/games/{placeId}/scripts ``` Returns scripts attached to a game, with the full [filter + sort + pagination](/scripts-api/endpoints/list#query-parameters) set. Defaults to `sortBy=score`. ```bash theme={null} curl "https://api.roscripts.io/v1/games/2753915549/scripts?key=0&sortBy=score&max=20" ``` ```json theme={null} { "result": { "placeId": 2753915549, "scripts": [ { "id": "lst_…", "title": "…", "loadstring": "https://script.roscripts.io/…" } ], "page": 1, "max": 20, "total": 64, "totalPages": 4, "nextPage": 2, "nextCursor": "eyJ2…" }, "meta": { "page": 1, "max": 20, "total": 64, "totalPages": 4, "nextPage": 2, "nextCursor": "eyJ2…" } } ``` # Get a script Source: https://docs.sirius.menu/scripts-api/endpoints/get GET https://api.roscripts.io/v1/scripts/{ref} Fetch one script's full detail, its raw source, or its version history. Resolve a single script by **id, slug, or shortId** — all three work in the same path: ```bash theme={null} curl "https://api.roscripts.io/v1/scripts/blox-fruits-hub" curl "https://api.roscripts.io/v1/scripts/lst_9f2c4a1b7d6e5f0a1234abcd" curl "https://api.roscripts.io/v1/scripts/Ab3xK9q" ``` ## Path parameters The script's `id`, `slug`, or `shortId`. ## Query parameters Include up to 6 related scripts (same game first). Set `0` to skip. Sparse fieldset for the script object. ## Response The full [Script object](/scripts-api/schema), including the long-form `body`. Related scripts, unless `related=0` or a `fields` set was requested. Returns `404 not_found` if the script doesn't exist, isn't live, or is hidden. *** ## Raw source ``` GET https://api.roscripts.io/v1/scripts/{ref}/raw ``` Returns the script's raw Lua source as `text/plain`. **Only free, keyless scripts are servable** — for everything else, prefer the [`loadstring`](/scripts-api/schema) URL. ```bash theme={null} curl "https://api.roscripts.io/v1/scripts/blox-fruits-hub/raw" ``` | Situation | Response | | -------------------- | ------------------------------------------------- | | Free, keyless | `200` with the Lua source | | Paid script | `402 payment_required` + `details.marketplaceUrl` | | Key-system script | `403 forbidden` + `details.keyLink` | | Not found / not live | `404 not_found` | A script object only includes a non-null `rawUrl` when its source is servable — check that first to avoid a `402`/`403`. For execution, `loadstring` is almost always what you want. *** ## Version history ``` GET https://api.roscripts.io/v1/scripts/{ref}/versions ``` Returns the changelog and metadata for each published version (newest first). Source is not included. ```json theme={null} { "result": { "listingId": "lst_…", "versions": [ { "id": "ver_…", "label": "v3", "changelog": "Fixed auto-farm after the May update", "sizeBytes": 18422, "uploadedAt": "2026-05-28T11:04:00Z" } ] } } ``` # Fetch scripts Source: https://docs.sirius.menu/scripts-api/endpoints/list GET https://api.roscripts.io/v1/scripts List and filter the catalogue. The workhorse endpoint. Returns a paginated, filterable, sortable list of live scripts. With no parameters it returns the most recently updated scripts (the "home feed"). ```bash theme={null} curl "https://api.roscripts.io/v1/scripts?sortBy=views&order=desc&max=20" ``` Pass `q` here to transparently route through search — but the dedicated [search endpoint](/scripts-api/endpoints/search) defaults to relevance sorting and adds `matched`. ## Query parameters Page number (ignored when `cursor` is set). Results per page. Max 20 anonymous, 100 with a key. Opaque keyset cursor from `meta.nextCursor` for stable deep paging. One of `views`, `likeCount`, `dislikeCount`, `createdAt`, `updatedAt`, `score`, `accuracy`. `asc` or `desc`. `free` or `paid`. Filter by key system. Filter by universal (not tied to a game). Verified-creator scripts only. Filter by patched state. Filter by creator username. Filter by Roblox place id. Filter by game name (exact, case-insensitive). Filter by tag. Filter by UI library (e.g. `Rayfield`). `direct` or `sirius`. Only scripts with at least this many 30-day views. Only scripts with a custom thumbnail. Exclude a specific script id. Unix seconds or ISO 8601 — only scripts updated since. Comma-separated sparse fieldset (e.g. `id,title,loadstring`). ## Response Array of [Script objects](/scripts-api/schema). Pagination — see [Pagination](/scripts-api/pagination). ```json theme={null} { "result": { "scripts": [ { "id": "lst_…", "title": "…", "loadstring": "https://script.roscripts.io/…" } ], "page": 1, "max": 20, "count": 20, "total": 412, "totalPages": 21, "nextPage": 2, "nextCursor": "eyJ2…" }, "meta": { "page": 1, "max": 20, "count": 20, "total": 412, "totalPages": 21, "nextPage": 2, "nextCursor": "eyJ2…" } } ``` # Suggest, stats & health Source: https://docs.sirius.menu/scripts-api/endpoints/misc Autocomplete, trending searches, catalogue stats, and the health check. ## Suggest (autocomplete) ``` GET https://api.roscripts.io/v1/suggest?q={q} ``` Lightweight autocomplete for search boxes — returns matching titles, tags, and games. Much cheaper than full search; use it for typeahead. At least 2 characters. Up to 15 per group. ```json theme={null} { "result": { "titles": ["Blox Fruits Hub", "Blox Fruits Auto Farm"], "tags": ["blox-fruits"], "games": [{ "placeId": 2753915549, "name": "Blox Fruits" }] } } ``` *** ## Trending searches ``` GET https://api.roscripts.io/v1/trending/searches ``` The most popular search queries over the last 7 days — great for a "trending" strip. Up to 50. ```json theme={null} { "result": { "queries": [ { "query": "blox fruits", "count": 1843 }, { "query": "fisch", "count": 1210 } ] } } ``` *** ## Catalogue stats ``` GET https://api.roscripts.io/v1/stats ``` Catalogue-wide totals. ```json theme={null} { "result": { "scripts": 41203, "games": 1208, "verifiedScripts": 5821, "freeScripts": 39740, "paidScripts": 1463 } } ``` *** ## Health ``` GET https://api.roscripts.io/v1/health ``` Liveness check with a database ping. Never cached. Returns `200` when healthy, `503` when degraded. ```json theme={null} { "result": { "status": "ok", "db": true, "time": "2026-05-31T23:00:00Z" } } ``` *** ## OpenAPI spec ``` GET https://api.roscripts.io/v1/openapi.json ``` The full machine-readable [OpenAPI 3.1](https://spec.openapis.org/oas/v3.1.0) description of the API — point Swagger UI, Postman, or a code generator at it. # Creators Source: https://docs.sirius.menu/scripts-api/endpoints/owners Public creator profiles and their scripts. ## Creator profile ``` GET https://api.roscripts.io/v1/owners/{username} ``` Public profile and aggregate stats for a creator. We never expose internal user ids or emails. The creator's username. ```json theme={null} { "result": { "owner": { "username": "somedev", "verified": true, "pro": true, "avatar": "https://…", "scriptCount": 12, "totalViews": 482301, "totalLikes": 9120 } } } ``` Returns `404` if the creator doesn't exist. *** ## Creator's scripts ``` GET https://api.roscripts.io/v1/owners/{username}/scripts ``` That creator's live scripts, with the full [filter + sort + pagination](/scripts-api/endpoints/list#query-parameters) set. Defaults to `sortBy=score`. ```bash theme={null} curl "https://api.roscripts.io/v1/owners/somedev/scripts?sortBy=views&max=20" ``` ```json theme={null} { "result": { "owner": "somedev", "scripts": [ { "id": "lst_…", "title": "…", "loadstring": "https://script.roscripts.io/…" } ], "page": 1, "max": 20, "total": 12, "totalPages": 1, "nextPage": null }, "meta": { "page": 1, "max": 20, "total": 12, "totalPages": 1, "nextPage": null, "nextCursor": null } } ``` # Search Source: https://docs.sirius.menu/scripts-api/endpoints/search GET https://api.roscripts.io/v1/scripts/search Relevance-ranked full-text search across the catalogue. Full-text search over titles, descriptions, tags, games, and creators. Typo-tolerant, with synonym handling (e.g. "autofarm" matches "auto farm"). Defaults to relevance (`accuracy`) sorting and annotates each result with the fields that matched. ```bash theme={null} curl "https://api.roscripts.io/v1/scripts/search?q=blox%20fruits&max=10" ``` ## Query parameters The search query. Max 20 anonymous, 100 with a key. `accuracy` (relevance), or any of `views`, `likeCount`, `dislikeCount`, `createdAt`, `updatedAt`, `score`. All [filters from the fetch endpoint](/scripts-api/endpoints/list#query-parameters) — `mode`, `key`, `universal`, `verified`, `patched`, `placeId`, `game`, `tag`, `ui`, `owner`, `minViews`, `hasThumbnail`, `fields` — work here too. Search is on a tighter per-minute budget than other endpoints (see [Rate limits](/scripts-api/rate-limits)). Debounce user typing — don't fire a request per keystroke. For autocomplete, use [`/v1/suggest`](/scripts-api/endpoints/misc#suggest) instead. ## Response Same shape as [fetch](/scripts-api/endpoints/list#response), plus: The query you searched. Which fields matched, e.g. `["title","tags"]`. ```json theme={null} { "result": { "query": "blox fruits", "scripts": [ { "id": "lst_…", "title": "Blox Fruits Hub", "matched": ["title", "game"], "loadstring": "https://script.roscripts.io/…" } ], "page": 1, "max": 10, "total": 41, "totalPages": 5, "nextPage": 2 }, "meta": { "page": 1, "max": 10, "total": 41, "totalPages": 5, "nextPage": 2, "nextCursor": null } } ``` # Trending Source: https://docs.sirius.menu/scripts-api/endpoints/trending GET https://api.roscripts.io/v1/scripts/trending The hottest scripts right now, by ranking score. Returns the top scripts by ranking score — a blend of votes, recent view velocity, and freshness. The ranking recomputes hourly, and responses are cached for 5 minutes, so polling faster than that just returns the same data. ```bash theme={null} curl "https://api.roscripts.io/v1/scripts/trending?max=20" ``` ## Query parameters Number of scripts. Max 20 anonymous, 100 with a key. Restrict trending to a single game. ## Response Top scripts, highest score first. ```json theme={null} { "result": { "max": 20, "scripts": [ { "id": "lst_…", "title": "…", "score": 6.12, "views": 40213, "loadstring": "https://script.roscripts.io/…" } ] } } ``` Want trending **search terms** instead of scripts? Use [`/v1/trending/searches`](/scripts-api/endpoints/misc#trending-searches). # Errors Source: https://docs.sirius.menu/scripts-api/errors The error envelope, status codes, and machine-readable error codes the API returns. When something goes wrong, the API returns a non-`2xx` status and a consistent JSON envelope with a machine-readable `code` you can branch on: ```json theme={null} { "error": { "code": "not_found", "message": "Script not found.", "details": { } } } ``` `message` is human-readable and may change; **`code` is stable** — switch on it, not on the message text. `details` is optional and only present when it helps (for example, `marketplaceUrl` on a paid-script `402`). ## Status codes | Status | `code` | When | | ------ | -------------------- | ------------------------------------------------------------------------- | | `400` | `bad_request` | A parameter is malformed (bad enum, non-numeric `placeId`, etc.) | | `401` | `unauthorized` | Reserved; the public API does not require auth | | `402` | `payment_required` | Raw source requested for a **paid** script — see `details.marketplaceUrl` | | `403` | `forbidden` | Raw source requested for a **key-system** script — see `details.keyLink` | | `404` | `not_found` | No such script, game, creator, or endpoint | | `405` | `method_not_allowed` | Wrong HTTP method for the endpoint | | `429` | `rate_limited` | [Rate limit](/scripts-api/rate-limits) exceeded — honour `Retry-After` | | `503` | `unavailable` | Temporary upstream issue — retry after a short delay | ## Validation errors Unknown query parameters are **ignored** (so we can add new ones without breaking you), but malformed *known* parameters return `400` with a clear message: ```json theme={null} { "error": { "code": "bad_request", "message": "'sortBy' must be one of: views, likeCount, dislikeCount, createdAt, updatedAt, score, accuracy" } } ``` ## Raw-source gating The [`/raw`](/scripts-api/endpoints/get#raw-source) endpoint only serves source for **free, keyless** scripts. Otherwise: ```json Paid (402) theme={null} { "error": { "code": "payment_required", "message": "This is a paid script; source is not available via the API.", "details": { "marketplaceUrl": "https://…" } } } ``` ```json Key system (403) theme={null} { "error": { "code": "forbidden", "message": "This script uses a key system; source is not available via the API.", "details": { "keyLink": "https://…" } } } ``` You can avoid these entirely: a script object only includes a `rawUrl` when its source is actually servable. Check `rawUrl != null` (or `scriptType === "free" && key === false`) before fetching raw. # Examples Source: https://docs.sirius.menu/scripts-api/examples Copy-paste integrations in Luau, JavaScript, and C#. Full working snippets for the most common task: search the catalogue and use the result. All examples are anonymous; add an `Authorization: Bearer rk_live_…` header for higher limits. ## Search and list ```lua Luau (executor) theme={null} local HttpService = game:GetService("HttpService") local function search(query) local url = ("https://api.roscripts.io/v1/scripts/search?q=%s&max=10") :format(HttpService:UrlEncode(query)) local ok, body = pcall(function() return game:HttpGet(url) end) if not ok then return {} end return HttpService:JSONDecode(body).result.scripts end for _, s in ipairs(search("blox fruits")) do print(("%s — %s%s"):format( s.title, s.verified and "✔ " or "", s.key and "[key]" or "" )) end ``` ```js JavaScript theme={null} async function search(query) { const url = `https://api.roscripts.io/v1/scripts/search?q=${encodeURIComponent(query)}&max=10`; const res = await fetch(url); if (!res.ok) throw new Error((await res.json()).error.message); const { result } = await res.json(); return result.scripts; } for (const s of await search("admin")) { console.log(s.title, s.slug, s.loadstring); } ``` ```csharp C# theme={null} using System.Net.Http.Json; using System.Text.Json; var http = new HttpClient(); var url = "https://api.roscripts.io/v1/scripts/search?q=admin&max=10"; var data = await http.GetFromJsonAsync(url); foreach (var s in data.GetProperty("result").GetProperty("scripts").EnumerateArray()) { Console.WriteLine($"{s.GetProperty("title").GetString()} — {s.GetProperty("slug").GetString()}"); } ``` ## Fetch one script and run it ```lua Luau (executor) theme={null} local HttpService = game:GetService("HttpService") local function getScript(ref) local url = "https://api.roscripts.io/v1/scripts/" .. ref return HttpService:JSONDecode(game:HttpGet(url)).result.script end local s = getScript("blox-fruits-hub") -- id, slug, or shortId all work if s.patched then warn("Heads up: this script may be patched.") end loadstring(game:HttpGet(s.loadstring))() ``` ```js JavaScript theme={null} const res = await fetch("https://api.roscripts.io/v1/scripts/blox-fruits-hub"); const { result } = await res.json(); const s = result.script; console.log(s.title, "by", s.owner?.username); console.log("Run with:", s.loadstring); ``` ## Browse a game ```js JavaScript theme={null} // Top scripts for Blox Fruits, no key systems const res = await fetch( "https://api.roscripts.io/v1/games/2753915549/scripts?key=0&sortBy=score&max=20" ); const { result } = await res.json(); console.log(`${result.total} scripts for placeId ${result.placeId}`); ``` ```lua Luau (executor) theme={null} local HttpService = game:GetService("HttpService") local placeId = game.PlaceId local url = ("https://api.roscripts.io/v1/games/%d/scripts?key=0&sortBy=score"):format(placeId) local res = HttpService:JSONDecode(game:HttpGet(url)) for _, s in ipairs(res.result.scripts) do print(s.title) end ``` ## Keep a local mirror in sync ```js JavaScript theme={null} let lastSeen = loadLastSeen() ?? "1970-01-01T00:00:00Z"; while (true) { const url = `https://api.roscripts.io/v1/scripts?updatedSince=${encodeURIComponent(lastSeen)}&sortBy=updatedAt&order=asc&max=100`; const { result } = await (await fetch(url)).json(); if (result.scripts.length === 0) break; for (const s of result.scripts) { upsert(s); if (s.updatedAt > lastSeen) lastSeen = s.updatedAt; } saveLastSeen(lastSeen); } ``` Building a wrapper library for a language we don't list here? Tell us on [Discord](https://discord.gg/KQ4kwzA87s) and we'll feature it. # RoScripts API Source: https://docs.sirius.menu/scripts-api/index A fast, open, read-only REST API for the RoScripts catalogue of Roblox scripts. Search, browse, and execute scripts straight from your hub or executor. The RoScripts API gives your script hub, executor, or site direct access to the entire RoScripts catalogue — search, filters, trending, per-game browsing, creator profiles, and a ready-to-run `loadstring` URL on every script. It's **open**: no key is required to get started. An optional [API key](/scripts-api/authentication) simply raises your rate limits and page sizes. Your first request in under a minute — no signup. Fetch, search, trending, games, owners, and more. Every field a script returns, and what it means. Cache, paginate, and sync the right way. ## Base URL ``` https://api.roscripts.io/v1 ``` All endpoints live under `/v1`. The version prefix is part of the contract — we won't make breaking changes within `v1`. ## Quickstart Hit the search endpoint. No authentication needed. ```bash theme={null} curl "https://api.roscripts.io/v1/scripts/search?q=blox%20fruits&max=3" ``` Every successful response is wrapped in a `result` object, with pagination in `meta`. ```json theme={null} { "result": { "query": "blox fruits", "scripts": [ { "id": "lst_9f2c…", "title": "Blox Fruits Hub", "slug": "blox-fruits-hub", "scriptType": "free", "verified": true, "key": false, "views": 18234, "game": { "placeId": 2753915549, "name": "Blox Fruits", "image": "https://…" }, "loadstring": "https://script.roscripts.io/Ab3xK9q", "updatedAt": "2026-05-28T11:04:00Z" } ], "page": 1, "max": 3, "total": 41, "totalPages": 14, "nextPage": 2, "nextCursor": "eyJ2Ijo…" }, "meta": { "page": 1, "max": 3, "total": 41, "totalPages": 14, "nextPage": 2, "nextCursor": "eyJ2Ijo…" } } ``` Every script carries a `loadstring` URL — a stable, cached execution endpoint. From an executor: ```lua theme={null} local hit = game:HttpGetAsync("https://api.roscripts.io/v1/scripts/search?q=blox%20fruits&max=1") local script = game:GetService("HttpService"):JSONDecode(hit).result.scripts[1] loadstring(game:HttpGet(script.loadstring))() ``` ## What you can do Page through the catalogue with filters for game, key-system, verified, free/paid, patched, UI library, and more. Relevance-ranked search with typo-tolerant matching and synonym handling. The hottest scripts right now, by a real ranking score. Resolve many scripts at once, or pull only what changed with updatedSince. ## How it compares RoScripts gives you everything a script-catalogue API should, plus the things hubs actually need: | Capability | RoScripts | | ---------------------------------------------- | ------------------------------------------------------------------ | | Fetch / search / trending | ✅ | | Per-script detail + raw source | ✅ | | **`loadstring` execution URL on every script** | ✅ | | **Batch resolve** (`POST /scripts/batch`) | ✅ | | **Incremental sync** (`updatedSince`) | ✅ | | **Cursor pagination** for stable deep paging | ✅ | | Per-game + per-creator browsing | ✅ | | ETag / `304 Not Modified` | ✅ | | Ranking `score` + community signals | ✅ | | Official OpenAPI spec | ✅ ([`/v1/openapi.json`](https://api.roscripts.io/v1/openapi.json)) | The API is read-only. Publishing and managing scripts happens on [roscripts.io](https://roscripts.io). Owners can mint API keys from their [account settings](https://roscripts.io/account). # Pagination & caching Source: https://docs.sirius.menu/scripts-api/pagination Page through results, switch to cursors for deep paging, and use ETags to make repeat reads free. List endpoints return a `meta` object (also mirrored inside `result`) with everything you need to page: ```json theme={null} "meta": { "page": 1, "max": 20, "count": 20, "total": 412, "totalPages": 21, "nextPage": 2, "nextCursor": "eyJ2IjoxNzg…" } ``` ## Page-based The simplest approach — `?page=2&max=50`. Good for jumping to a specific page and showing "Page X of Y". ```bash theme={null} curl "https://api.roscripts.io/v1/scripts?page=2&max=50&sortBy=views" ``` `max` is capped at **20** for anonymous callers and **100** with a key. `total` and `totalPages` tell you when to stop; `nextPage` is `null` on the last page. ## Cursor-based (recommended for deep paging) Page-based pagination can skip or repeat items when the underlying data changes between requests. For stable iteration over large result sets — and for syncing — use the opaque `nextCursor`: ```bash theme={null} # First page curl "https://api.roscripts.io/v1/scripts?max=100&sortBy=createdAt" # → meta.nextCursor = "eyJ2IjoxNzg…" # Next page: pass the cursor back curl "https://api.roscripts.io/v1/scripts?max=100&sortBy=createdAt&cursor=eyJ2IjoxNzg…" ``` When you pass a `cursor`, `page` is ignored and the count is skipped (so `total` is `null`) — keep paging until `nextCursor` comes back `null`. A cursor is tied to the `sortBy`/`order` you used to create it. Keep them consistent across a paging run. ## Incremental sync To keep a local mirror fresh, sort by `updatedAt` ascending and pass `updatedSince` (a Unix timestamp or ISO 8601 date). You'll only get scripts changed since then: ```bash theme={null} curl "https://api.roscripts.io/v1/scripts?updatedSince=2026-05-01T00:00:00Z&sortBy=updatedAt&order=asc&max=100" ``` Store the largest `updatedAt` you've seen and pass it as the next `updatedSince`. See [Best practices → Sync, don't re-scrape](/scripts-api/best-practices#sync-don-t-re-scrape). ## Caching & ETags Every cacheable response carries `Cache-Control` and an `ETag`. Send the ETag back as `If-None-Match` and you'll get a `304 Not Modified` with no body when nothing changed — saving bandwidth and **not counting against your rate limit**. ```bash theme={null} # First request returns an ETag curl -i "https://api.roscripts.io/v1/scripts/trending" # → ETag: W/"a1b2c3…" # Send it back; get 304 if unchanged curl -i "https://api.roscripts.io/v1/scripts/trending" \ -H 'If-None-Match: W/"a1b2c3…"' # → HTTP/1.1 304 Not Modified ``` The `X-Cache` response header tells you whether you hit the edge cache (`HIT`), came from origin (`MISS`), or were served a stale-but-safe copy during an upstream hiccup (`STALE`). # Rate limits Source: https://docs.sirius.menu/scripts-api/rate-limits Per-minute and per-day request budgets, the headers we return, and how to handle 429s gracefully. Every request is rate-limited to keep the API fast and fair. Limits are applied per **API key** when you send one, otherwise per **IP address**. Adding a key raises every limit below. ## Budgets There are two windows — a per-minute burst limit and a per-day quota. Search and raw-source endpoints have their own (lower) per-minute limits because they're heavier. | | Anonymous | Free key | Partner | | --------------------- | --------- | -------- | --------- | | Requests / minute | 120 | 600 | 3,000 | | Requests / day | 10,000 | 200,000 | 5,000,000 | | Search / minute | 30 | 120 | 600 | | Raw source / minute | 30 | 120 | 600 | | Max `max` (page size) | 20 | 100 | 100 | The cheapest way to raise your effective throughput isn't a bigger key — it's [caching](/scripts-api/best-practices#cache-everything). Responses are served from the edge and carry `ETag`s, so well-behaved clients rarely touch the rate limiter at all. ## Headers Every response includes your current standing on the per-minute window: ```http theme={null} X-RateLimit-Limit: 120 X-RateLimit-Remaining: 117 X-RateLimit-Reset: 60 X-RateLimit-Tier: anon ``` | Header | Meaning | | ----------------------- | --------------------------------------------- | | `X-RateLimit-Limit` | Requests allowed in the current minute window | | `X-RateLimit-Remaining` | Requests left in this window | | `X-RateLimit-Reset` | Seconds until the window resets | | `X-RateLimit-Tier` | `anon`, `free`, or `partner` | Requests served from the edge cache don't count against your budget — only requests that reach the origin do. A cache `HIT` (see the `X-Cache` header) is effectively free. ## Handling 429 When you exceed a limit you get a `429` with a `Retry-After` header (in seconds) and the standard error envelope: ```json theme={null} { "error": { "code": "rate_limited", "message": "Rate limit exceeded. Slow down or use an API key for higher limits." } } ``` Back off and retry after the indicated delay. A simple, correct pattern: ```js JavaScript theme={null} async function call(url, opts) { for (let attempt = 0; attempt < 5; attempt++) { const res = await fetch(url, opts); if (res.status !== 429) return res; const wait = Number(res.headers.get("retry-after") ?? 2) * 1000; await new Promise((r) => setTimeout(r, wait)); } throw new Error("rate limited after retries"); } ``` ```lua Luau theme={null} local function call(url) for _ = 1, 5 do local ok, body = pcall(game.HttpGet, game, url) if ok then return body end task.wait(2) -- back off; executors rarely expose Retry-After on HttpGet end error("rate limited after retries") end ``` Don't hammer through a `429` in a tight loop — repeated violations can get an IP temporarily blocked at the edge. Respect `Retry-After`. # The Script object Source: https://docs.sirius.menu/scripts-api/schema Every field a script returns across the API, with types and meaning. List and detail endpoints return scripts in a single consistent shape. Detail endpoints add a few extra fields (noted below). Parse defensively — new fields may be added over time. ```json theme={null} { "id": "lst_9f2c4a1b7d6e5f0a1234abcd", "slug": "blox-fruits-hub", "shortId": "Ab3xK9q", "title": "Blox Fruits Hub", "description": "Auto-farm, teleports, and a clean UI.", "scriptType": "free", "verified": true, "key": false, "keyLink": null, "universal": false, "patched": false, "views": 18234, "viewsTotal": 92841, "likeCount": 1204, "dislikeCount": 33, "excellentCount": 210, "saveCount": 880, "commentCount": 47, "score": 4.213, "game": { "placeId": 2753915549, "name": "Blox Fruits", "image": "https://…" }, "image": "https://roscripts.io/api/listings/lst_…/thumbnail?v=1748400000", "tags": ["auto-farm", "teleport"], "ui": "Rayfield", "owner": { "username": "somedev", "verified": true, "pro": true, "avatar": "https://…" }, "loadstring": "https://script.roscripts.io/Ab3xK9q", "rawUrl": "https://api.roscripts.io/v1/scripts/lst_…/raw", "marketplaceUrl": null, "discordUrl": "https://discord.gg/…", "createdAt": "2026-03-02T18:20:00Z", "updatedAt": "2026-05-28T11:04:00Z" } ``` ## Identity Stable, opaque listing id (`lst_…`). Use this to reference a script long-term. Human-readable URL segment — `https://roscripts.io/s/{slug}`. May change if the owner renames. Short code behind the `loadstring` URL (`script.roscripts.io/{shortId}`). ## Content Short blurb shown on cards. Long-form markdown description. **Detail endpoint only.** UI library, e.g. `Rayfield`. 16:9 thumbnail URL, or `null` if the listing uses an auto-generated card. ## Classification & trust The creator is a verified RoScripts creator. Whether the script gates execution behind a key system. Where to get the key, when `key` is `true`. Detail responses. `true` when the script isn't tied to a specific game. Flagged by the owner/staff as no longer working. ## Stats Rolling 30-day view count (drives ranking). All-time view count. Top-tier "excellent" votes — a stronger positive signal than a like. Computed ranking score (higher is better). Combines votes, view velocity, and freshness. ## Game & creator `{ placeId, name, image }`, or `null` for universal scripts. `{ username, verified, pro, avatar }`. We never expose internal user ids or emails. ## Links Direct execution URL — `loadstring(game:HttpGet(...))()`. `null` only when no short code exists. Where to fetch raw source. Only present when the source is actually servable (free + keyless). Purchase link for paid scripts. Optional community/support link. ## Timestamps ISO 8601. ISO 8601. Use with `updatedSince` for syncing. ## Search-only On search results, which fields matched the query (e.g. `["title","tags"]`).