What is this?

clover's Lua 5.4 scripting engine, with a Roblox-style API for reading the game, drawing overlays and building menus.

An external Lua VM

Scripts run in clover, not inside Roblox. game, workspace and Instance read Roblox memory, so only part of the Roblox API exists. Use pcall when unsure.

Write scripts in the Lua VM editor and run them with Ctrl+Enter. All scripts share one VM; callbacks, tasks and signals keep running about 60 times per second.

Why use it

  • Real Lua 5.4. Integers and floats, bitwise operators, integer division, goto and the utf8 library, plus familiar helpers such as table.find, math.clamp, string.split and Random.new.
  • Roblox-style API. game, workspace, Instance, Vector2, Vector3, CFrame, Color3, Enum, task, RunService, UserInputService and TweenService are available with Roblox names.
  • Executor compatibility. Common executor functions are built in, including readfile, getgenv, loadstring, decompile, getgc, fireserver, queue_on_teleport, crypt and WebSocket.
  • Fast overlays. Immediate-mode draw.* calls (up to 500000 commands per frame) and retained Drawing.new objects render on top of the game.
  • Menus in a few lines. Clover:CreateWindow builds windows with tabs, sections, buttons, toggles, sliders, inputs, dropdowns, keybinds and color pickers, and can save and load configs.
  • Any naming style. Helper namespaces such as draw, utility, mouse and file accept PascalCase, snake_case and camelCase names for the same function.
  • Safe by default. There is no io, no require and only a small os subset. HTTP, WebSocket, the clipboard, setfflag and the debug library stay off until you allow them, files never leave the workspace folder, the Lua heap is capped at 1 GB, and a runaway loop can always be stopped with Reset VM.
  • Teleport aware. Scripts queued with queue_on_teleport run in a fresh VM after the game teleports.
  • Agent ready. A built-in MCP server lets AI coding agents run Lua, read the log, edit editor tabs, manage workspace files and inspect the game.

Where to go next

Setting UpOpen the Lua VM window, run your first script and find the workspace folder your scripts can read and write.Core ConceptsHow the clover VM is built and reset, how it schedules your code, and how it reports output and errors.Sandbox & SafetyWhat's removed from standard Lua, what the unsafe API unlocks, and how files are confined.GlobalsEvery global name and where it's documented, plus the small global functions.gameHandles to the Roblox DataModel and Workspace, plus GetService and IsLoaded.drawImmediate-mode 2D drawing that you repeat every frame, plus world-to-screen projection.DrawingRetained objects: create a Line, Text, Circle or Square once and it stays on screen until removed.uiBuild windows with tabs, sections and elements, with change callbacks and saved configs.taskSleep, spawn, delay and defer coroutines.SignalsEvent objects with Connect, Once, Wait and Fire, and every signal the engine exposes.httpWeb requests and JSON.MCP ServerLet AI agents on your PC run Lua, edit script tabs, browse the game and read memory.

Core Concepts

How the clover VM is built and reset, how it schedules your code, and how it reports output and errors.

One VM for every script

All scripts share one Lua state. Callbacks, connections, tasks and windows keep running until the VM resets, so press Reset VM before re-running a script.

Top-level code blocks the VM

While top-level code runs, nothing else in the VM does. Put long work in task.spawn with task.wait.

lifecycle.lua
local steps = 0

clover.on_tick(function()
    steps = steps + 1
end)

clover.on_render(function()
    draw.Text(20, 40, "scheduler steps: " .. steps, 1, 1, 1, 1)
end)

task.spawn(function()
    while true do
        task.wait(5)
        print("steps so far:", steps)
    end
end)

print("setup done")

VM lifecycle

There is exactly one VM. clover starts it while it starts its modules, and the first Run starts it if it is not running yet. It is stopped when clover exits. In between it can be reset any number of times.

How the VM is built

  1. A new Lua state is created with an allocator that caps the VM at 1 GB of Lua memory.
  2. The standard libraries are opened and restricted (see Sandbox & Safety). debug is opened only when the unsafe API is allowed.
  3. The clover table is registered, together with global shortcuts such as readfile, getgenv, tick, typeof, notify and WorldToScreen.
  4. The Roblox-style layer is registered: the data types, print and warn, task, bit32, the services and HTTP functions, Drawing, Instance, game and workspace, the script and GC helpers, WebSocket and crypt. Then three Lua chunks add the standard library extensions, Enum with the input and service helpers, and the compatibility shims.
  5. clover.ui is registered, the instruction-count hook is installed, and the Lua prelude builds the draw, utility, keyboard, mouse, cheat and file tables and the Clover menu library.

The scheduler step

A dedicated script thread drives the VM in a loop. Each pass, called a step here, does the following and then sleeps for 16 ms, so the VM steps roughly 60 times per second at most:

  1. Apply a pending reset, if one was requested.
  2. If the master switch is off, clear the draw list and skip the rest of the step.
  3. Run every queued chunk in order: editor runs, MCP lua_execute calls and teleport scripts.
  4. Call every tick callback, then every render callback.
  5. Pump the engine: refresh game and workspace and fire player and child signals, advance tweens, resume task.wait threads whose time is up, start due task.delay and task.defer functions, fire the RunService step signals (Heartbeat, RenderStepped, Stepped) with the step's delta time, deliver key and mouse input events and WebSocket events, then run queued UI callbacks.
  6. Publish the draw commands queued during this step to the overlay.

Reset

A reset throws the whole Lua state away and builds a new one at the start of the next step. It happens when:

  • you press Reset VM in the Lua VM window, or an agent calls the MCP lua_reset tool;
  • the Allow unsafe Lua API setting changes, from the settings card or the MCP lua_set_unsafe_api tool;
  • a teleport is detected while scripts are queued with queue_on_teleport. The queued scripts then run in the new VM (see Teleport).

A reset stops running code (execution interrupted), cancels queued chunks and clears every global, callback, task, connection, tween, Drawing, window and WebSocket. It also empties the teleport queue.

Master switch

Scripts only run while the master switch is on. With it off, Run is refused and running code stops with master switch is off. The VM isn't reset, so callbacks resume when it's turned back on.

Running code

Each run is compiled as a chunk named script and started in a new coroutine, so top-level code can call task.wait.

Because of the chunk name, positions in error messages look like [string "script"]:12:.

Callbacks

Code that must run on every step is registered as a callback instead of being written as a loop. There are two callback lists, documented on the cheat page:

Register withListCalled
clover.on_tick(fn), cheat.register("onPulse", fn), cheat.register("onStep", fn)tickEvery step, before the render list.
clover.on_render(fn), cheat.register("onDraw", fn), cheat.register("onFrame", fn)renderEvery step, after the tick list.
  • Both lists run on the script thread, in registration order, with no arguments. Render callbacks are the place for draw.* calls, because the draw list is rebuilt every step.
  • Callbacks are called with a plain protected call on the VM's main thread, so they cannot yield: calling task.wait inside one raises an error. Start a task with task.spawn when a callback needs to wait.
  • A callback that raises an error is logged as [clover error 3] and removed for good. There is no function to unregister a callback; it stays until it errors or the VM resets.
  • cheat.register silently ignores event names it does not know.

Tasks and signals

task is a coroutine scheduler. task.wait sleeps (rounded up to whole ticks), task.spawn runs a function now, and task.delay/task.defer run one later. An error ends only that task.

Signals like RunService.Heartbeat and InputBegan fire during each tick. Handlers can't yield, and one that errors is disconnected. :Wait() pauses until the next fire.

Naming conventions

Engine functions live on clover (for example clover.draw_line) and are re-exported in PascalCase, snake_case and camelCase. All spellings are the same function.

NamespacePascalCasesnake_casecamelCaseRaw function
drawdraw.RectangleFilleddraw.rectangle_filleddraw.rectangleFilledclover.draw_rect_filled
utilityutility.GetMousePosutility.get_mouse_posutility.getMousePosclover.utility_get_mouse_pos
keyboardkeyboard.IsKeyDownkeyboard.is_key_downkeyboard.isKeyDownclover.is_key_down
mousemouse.RightClickmouse.right_clickmouse.rightClickclover.mouse2click
cheatcheat.GetWindowSizecheat.get_window_sizecheat.getWindowSizeclover.cheat_get_window_size
filefile.Readfile.readsame as snake_caseclover.file_read
  • Single-word names have two spellings: draw.Line and draw.line, file.Read and file.read.
  • A few raw functions have a second raw name: clover.printl for clover.print, clover.errorl for clover.error, clover.getbase for clover.module_base, clover.iskeypressed for clover.is_key_down and clover.WorldToScreen for clover.world_to_screen.
  • Frequently used functions are also globals: readfile, writefile, appendfile, isfile, isfolder, makefolder, delfile, listfiles, getgenv, tick, typeof, base64encode, base64decode, notify, WorldToScreen and world_to_screen. The Globals page lists every global name.
  • Roblox-style APIs keep Roblox's PascalCase (game:GetService, Instance.new, Vector3.new), and the standard library adds capitalized Roblox-era globals such as Wait, Spawn and Delay.
  • The menu library is the global Clover and uses PascalCase methods such as Clover:CreateWindow and Window:AddTab (see ui).

Printing and the log

Output goes to one log (newest 4096 lines), shown in Output and Console and readable through the MCP server.

FunctionSeveritySourceArguments joined with
print(...)infovma tab
warn(...)warnvma tab
clover.print(...), clover.printl(...)infoluaa space
clover.warn(...)warnluaa space
clover.error(...), clover.errorl(...)errorluaa space

Arguments are converted with tostring. clover.error only logs; use error to stop code. For toasts use notify (see Globals).

Console prefixMCP sourceLines from
[lua]luaclover.print, clover.warn, clover.error and errors raised by UI element callbacks.
[vm]vmThe global print and warn, engine messages such as queued, loaded and vm reset complete, [clover error N] reports, and task: and signal: errors.
[rbx]rbxSelected output, warning and error lines tailed from the newest Roblox client log in %LOCALAPPDATA%\Roblox\logs. The MCP lua_get_log tool hides them unless asked.

The console does not show the severity. It colors a line red when its text contains error and amber when it contains warn.

Errors

Errors never escape the VM. Where an error is raised decides how it is reported and what survives it:

Raised inLogged asConsequence
Compiling a run (syntax error)[clover error 1]: [string "script"]:LINE: messageThe chunk does not run.
Top-level code of a run[clover error 2]: messageThe rest of the chunk is skipped. Anything it already registered stays.
A tick or render callback[clover error 3]: messageThe callback is removed.
A task started by task.spawn, task.delay or task.defer, or any coroutine resumed by the scheduler (including a run's top-level code after its first task.wait)task: messageThat coroutine ends.
A signal connection callbacksignal: messageThe connection is disconnected.
A UI element callbackui callback: message (source lua)The element keeps working.
Code stopped by a resetNothingInterruption errors are not logged.

Console lines carry the [vm] or [lua] prefix in front of these texts. The MCP lua_execute tool also returns the error of its own chunk with a full traceback.

Error codes

CodeNameMeaning
0okSuccess. Never logged.
1compileA run failed to compile.
2runtimeA run's top-level code raised an error.
3callback_failedA tick or render callback raised an error and was removed.
10ui_window_not_foundclover.ui.add_tab or clover.ui.window_open_dialog got an unknown window id.
11ui_tab_not_foundclover.ui.add_section got an unknown tab id.
12ui_section_not_foundReserved. Not emitted at the moment.
13ui_parent_not_foundA clover.ui.add_* element function got an id that is neither a section nor a tab.
14ui_element_not_foundReserved. Not emitted at the moment.
15ui_bootstrapA built-in Lua chunk (the UI prelude or a standard library chunk) failed to load.
20mem_bad_typeReserved. Not emitted at the moment.
30bind_bad_argReserved. Not emitted at the moment.
31api_misuseReserved. Not emitted at the moment.

Reports are written as [clover error N]: message with severity error and source vm.

Time and memory limits

  • No execution time limit. A script may run as long as it likes. A loop that never yields freezes the VM (no callbacks, tasks or draws) until you press Reset VM or turn the master switch off.
  • Always interruptible. An instruction-count hook runs every 1000 VM instructions, and long string pattern matches check periodically, so even while true do end stops on a reset. A C function that is blocked in the middle of a call, such as an HTTP request, finishes first.
  • Bounded MCP waits. lua_execute waits 100 to 120000 ms (10000 by default) for its chunk. When the wait runs out it reports a timeout, but the script keeps running until it finishes or the VM is reset.
  • 1 GB of Lua memory. The VM's allocator refuses to grow past 1 GB in total, and the allocation then fails with Lua's not enough memory error. The MCP lua_status tool reports the current usage.

The other caps (draw commands, file sizes, listings) are listed under Sandbox & Safety.

Sandbox & Safety

What's removed from standard Lua, what the unsafe API unlocks, and how files are confined.

The unsafe API is off by default

HTTP, WebSocket, clipboard, setfflag and debug need Allow unsafe Lua API in Settings > Lua VM. It's off on every start, and changing it resets the VM.

Files stay in the workspace

Every file function works inside C:\clover\workspace\ only. Allowing the unsafe API does not loosen that.

unsafe-check.lua
local ok, result = pcall(HttpGet, "https://example.com")
if ok then
    print("downloaded", #result, "bytes")
else
    warn(result)
end

Standard libraries

The VM opens a fixed set of Lua 5.4 libraries and then removes or replaces the parts that could reach outside the sandbox.

LibraryAvailableNotes
baseYesprint and warn write to the clover log. load and loadstring accept text only (see below). dofile and loadfile are removed and replaced by workspace-only versions (see Executor Compatibility). require is removed.
coroutineYesStandard.
tableYesStandard, plus the extensions.
stringYesStandard, plus string.split. string.dump works, but its output cannot be loaded back.
mathYesStandard, plus the extensions.
utf8YesStandard.
osPartlyOnly os.clock, os.time, os.date and os.difftime.
ioNoRemoved.
packageNoRemoved, together with require.
debugUnsafe API onlyThe standard library is opened only when the unsafe API is allowed. debug.getupvalue, debug.getupvalues, debug.setupvalue and debug.getinfo always exist, but they are the engine's versions that work on game functions (see Garbage Collector).
bit32YesProvided by the engine, since Lua 5.4 has none (see bit32).

Loading code

load and loadstring are the same function. They only compile Lua source text: precompiled bytecode and the standard reader-function form are rejected.

load(source, [chunkname], [mode], [env])

Compiles source as a Lua chunk and returns it as a function without running it. A leading UTF-8 byte order mark is skipped.

Parameters:

  • source: string - Lua source text. Passing a function, as standard Lua allows, raises an argument error.
  • chunkname: string (optional, default "script") - Name used in error messages. As in standard Lua, a name starting with = or @ is shown as written, anything else as [string "..."].
  • mode: string (optional, default "t") - Must be exactly "t". Any other value, including "b" and "bt", makes the call return nil and Only text Lua chunks are allowed.
  • env: table (optional) - When this argument is present it becomes the chunk's _ENV. Passing an explicit nil therefore gives the chunk no environment at all.

Returns:

  • chunk: function? - The compiled chunk, or nil if compiling failed.
  • message: string? - The syntax error message when chunk is nil.

Aliases: loadstring

lua
local chunk, problem = loadstring("return 6 * 7", "=calc")
if chunk then
    print(chunk())
else
    warn(problem)
end

local scoped = load("print(answer)", "=scoped", "t", {print = print, answer = 42})
scoped()

The os subset

os is rebuilt as a new table that holds only four standard functions. os.exit, os.execute, os.getenv, os.remove, os.rename, os.tmpname and os.setlocale do not exist.

FunctionBehavior
os.clock()Standard Lua: elapsed seconds as a float, for measuring intervals.
os.time([t])Standard Lua: the current time, or the time described by table t.
os.date([format], [time])Standard Lua: a formatted date string, or a date table with "*t".
os.difftime(t2, t1)Standard Lua: the difference in seconds.

Allow unsafe Lua API

Enable it in Settings > Lua VM > Allow unsafe Lua API and confirm the dialog. The MCP server can also change it without a dialog.

Every change resets the VM, because the set of opened libraries is fixed when a VM is built. The log then shows Unsafe Lua APIs enabled. Files remain confined to the Clover workspace. or Unsafe Lua APIs disabled; running scripts are being reset.

FeatureFunctionsWithout the unsafe API
HTTPrequest, http_request, HttpGet, game:HttpGet, game:HttpGetAsync, HttpService:GetAsync, HttpService:RequestAsync, HttpService:PostAsync (see http)Raises HTTP access requires Allow unsafe Lua API.
WebSocketWebSocket.connect (see WebSocket)Raises WebSocket access requires Allow unsafe Lua API.
Clipboardclover.setclipboard and utility.SetClipboard, clover.utility_get_clipboard and utility.GetClipboard, with all their spellings (see utility)Raises Clipboard access requires Allow unsafe Lua API.
Fast flagssetfflag (see FFlags)Raises setfflag requires Allow unsafe Lua API. setfpscap calls setfflag inside pcall, so it silently does nothing.
Debug librarydebug.traceback, debug.sethook, debug.getlocal, debug.getmetatable, debug.setmetatable, debug.getregistry and the rest of the standard debug tableAbsent (nil).

Workspace file sandbox

All file access is confined to C:\clover\workspace\. This covers the file table, the global file functions, loadfile, dofile, configs and the MCP workspace_* tools. Rules:

  • Paths are relative to the workspace. Separate folders with / or \, for example configs/main.cfg.
  • Absolute and drive paths such as C:\x, C:x, /x or \\server\share are refused, and so are . and .. components.
  • A path component may not be empty, end with a dot or a space, contain : < > " | ? * or a control character, or be a reserved Windows device name (CON, PRN, AUX, NUL, CONIN$, CONOUT$, CLOCK$, COM0 to COM9, LPT0 to LPT9), with or without an extension.
  • Paths must be valid UTF-8, contain no NUL byte and be at most 4096 bytes long. An empty path is refused, except that listing an empty path lists the workspace root.
  • Symbolic links, junctions and other reparse points are refused anywhere along the path, files with more than one hard link are refused, and only regular disk files can be opened. The folders along the path are held open while an operation runs, so they cannot be swapped for a link half-way through.
  • Reads and writes are limited to 16 MB (16777216 bytes) per file, and appendfile refuses to grow a file past that size.
  • Writing creates missing parent folders and replaces the whole file.
  • Deleting works on files only; folders cannot be deleted.
  • Listing returns at most 10000 names and skips links and names that break the rules above.
  • The global file functions and the MCP workspace_list tool ignore trailing slashes. The file.* functions and the other MCP workspace tools do not, so file.read("notes/") is refused.

How a refusal is reported depends on the function. readfile, writefile, appendfile, makefolder, delfile and listfiles raise an error, while isfile, isfolder and the file.* functions return false or nil instead. See file for each function.

Limits

ResourceLimit
Lua memory1 GB for the whole VM. Allocations past it fail with not enough memory.
Execution timeNone. Stop runaway code with Reset VM.
Draw commands500000 per scheduler step. Extra commands are dropped and a warning is logged once until the next reset.
File size16 MB per read or write.
Directory listing10000 entries.
Path length4096 bytes.
HTTP response body64 MB. Larger responses fail with response too large.
LogThe newest 4096 lines.

Stopping scripts

Scripts can't hang clover: the VM runs on its own thread and a hook checks every 1000 instructions. A reset or the master switch interrupts running code. See Core Concepts.

Standard Library

Which Lua 5.4 libraries are loaded, and the Roblox-style helpers clover adds to table, math and string.

clover runs Lua 5.4.7, not Luau. //, bitwise operators and goto work; continue, += and type annotations don't.

Loaded libraries

LibraryStatusNotes
baseLoadedload and loadstring only accept text chunks; dofile, loadfile and require are removed (see Executor Compatibility for the workspace-based loadfile/dofile shims).
coroutineLoadedFull library.
tableLoadedPlus the extensions below.
stringLoadedPlus string.split.
mathLoadedPlus the extensions below.
utf8LoadedFull library.
osPartialOnly os.clock, os.time, os.date and os.difftime.
debugUnsafe onlyLoaded only while the unsafe API is enabled. See Sandbox & Safety.
io, packageRemovedNo direct file or module access. Use the file API instead.
bit32EngineProvided by clover, see bit32.

table extensions

Each function is only installed when the name is not already defined.

table.find(t, value, [init])

Linear search of the array part of t for value using ==.

Parameters:

  • t: table - Array to search.
  • value: any - Value to look for.
  • init: integer (optional, default 1) - Index to start from.

Returns:

  • integer? - Index of the first match, or nil.
lua
local names = {"Head", "Torso", "HumanoidRootPart"}
print(table.find(names, "Torso"))

table.clone(t)

Shallow copy of every key and value in t. The copy keeps the original metatable.

Parameters:

  • t: table - Table to copy.

Returns:

  • table - The new table.

table.clear(t)

Removes every key from t in place.

Parameters:

  • t: table - Table to empty.

table.create(count, [value])

Creates an array of count copies of value. When value is nil the result is an empty table.

Parameters:

  • count: integer - Number of slots to fill.
  • value: any (optional) - Value stored in every slot.

Returns:

  • table

table.getn(t)

Returns #t.

Parameters:

  • t: table

Returns:

  • integer

table.foreach(t, callback)

Calls callback(key, value) for every pair. Stops early and returns the callback's result as soon as it returns a non-nil value.

Parameters:

  • t: table
  • callback: function - Receives (key, value).

Returns:

  • any - The first non-nil callback result, or nothing.

table.foreachi(t, callback)

Like table.foreach, but walks indices 1 to #t in order and calls callback(index, value).

Parameters:

  • t: table
  • callback: function - Receives (index, value).

Returns:

  • any - The first non-nil callback result, or nothing.

math extensions

math.clamp(value, low, high)

Returns low when value < low, high when value > high, otherwise value.

Parameters:

  • value: number
  • low: number
  • high: number

Returns:

  • number

math.sign(value)

Returns 1, -1 or 0.

Parameters:

  • value: number

Returns:

  • integer

math.round(value)

Rounds half away from zero, so 2.5 becomes 3 and -2.5 becomes -3.

Parameters:

  • value: number

Returns:

  • number
FunctionEquivalent
math.pow(a, b)a ^ b
math.atan2(y, x)math.atan(y, x)
math.ldexp(m, e)m * 2.0 ^ e
math.log10(x)math.log(x, 10)
math.cosh(x)(math.exp(x) + math.exp(-x)) / 2
math.sinh(x)(math.exp(x) - math.exp(-x)) / 2
math.tanh(x)(e^x - e^-x) / (e^x + e^-x)

string extensions

string.split(text, [separator])

Splits text on a plain (non-pattern) separator. An empty separator splits into single characters. Consecutive separators produce empty strings.

Parameters:

  • text: string
  • separator: string (optional, default ",") - Plain text to split on.

Returns:

  • {string} - The pieces, in order.

Aliases: text:split(separator)

lua
for _, part in ipairs(("a,b,,c"):split(",")) do
    print(part)
end

Extra globals

GlobalDescription
unpackSame as table.unpack.
sharedAn empty table shared by every script in the VM.
time()Seconds since the standard library loaded, from os.clock.
elapsedTime()Same as time.
gcinfo()Lua heap size in KB, from collectgarbage("count").
delay(seconds, callback, ...)Calls task.delay.
DelaySame as delay.
Spawn(callback, ...)Calls task.spawn.
WaitSame as the global wait, see task.

Random

A Roblox-style random number generator built on a 64-bit xorshift. Every Random object keeps its own state, so two objects created with the same seed produce the same sequence.

random.lua
local rng = Random.new(1234)
print(rng:NextInteger(1, 6))
print(rng:NextNumber())
print(rng:NextNumber(10, 20))
local deck = rng:Shuffle({1, 2, 3, 4, 5})
print(table.concat(deck, " "))

Random.new([seed])

Creates a generator. Without a seed it mixes os.time, os.clock and an internal counter, so two calls in the same frame still differ. Non-integer seeds are floored.

Parameters:

  • seed: number (optional) - Seed value.

Returns:

  • Random

Random:NextNumber([low], [high])

With no arguments, returns a float in [0, 1). With both arguments, returns low + u * (high - low).

Parameters:

  • low: number (optional)
  • high: number (optional) - Required when low is given.

Returns:

  • number

Random:NextInteger(low, high)

Returns an integer from low to high, both inclusive.

Parameters:

  • low: integer
  • high: integer

Returns:

  • integer

Random:NextUnitVector()

Returns a random direction of length 1.

Returns:

  • Vector3

Random:Shuffle(list)

Shuffles the array in place (Fisher-Yates) and returns the same table.

Parameters:

  • list: table

Returns:

  • table - The same table, shuffled.

Random:Clone()

Returns a new generator with the same current state.

Returns:

  • Random

Setting Up

Open the Lua VM window, run your first script and find the workspace folder your scripts can read and write.

Master switch

Scripts only run while clover's master switch is on. With it off, Run logs master switch is off; script ignored, and code that is already running stops with master switch is off.

Running your first script

  1. Open the Lua VM

    In the clover menu, go to Settings and find the Lua VM card. Click Open Lua VM (it reads Focus Lua VM when the window is already open).

  2. Write some code

    The window opens with a tab named untitled.lua that already contains a hello-world line. The editor autocompletes globals and API names as you type.

    lua
    print("hello from clover")
    notify("clover is running")
  3. Run it

    Click Run or press Ctrl+Enter. The script is queued and runs on the VM thread on its next tick. Its print output and any error show up in the Output panel under the editor and in the Console tab of the side panel.

  4. Keep it running

    A script ends when its main chunk returns, but anything it registered keeps running: render callbacks, task timers and signal connections. See Core Concepts.

    lua
    local frames = 0
    local white = { R = 1, G = 1, B = 1, A = 1 }
    cheat.register("onDraw", function()
        frames = frames + 1
        draw.Text(20, 20, "frames: " .. frames, white)
    end)

The Lua VM window

ControlWhat it does
Run (Ctrl+Enter)Queues the active tab's code. Every run shares the same VM, so globals from earlier runs are still there.
Reset VMStops every running script, clears queued scripts and drawings, and restarts the VM with a clean state.
Clear editorEmpties the active tab.
Editor settingsCursor style (Bar, Block, Underscore), blink and blink rate.
TabsEach tab is a separate script. New tabs are named untitled-<n>.lua.
Output panelEverything the VM prints, with Copy and Clear buttons.
Side panelExplorer, MCP (the MCP Server controls) and Console (live output).

Running the same script twice registers its callbacks twice. Press Reset VM before running a new version of a script that hooks callbacks or signals.

The workspace folder

Scripts can only touch files inside C:\clover\workspace\. clover creates the folder on first use. Every path you pass to file functions and to readfile, writefile, listfiles, loadfile and dofile is relative to it.

save.lua
writefile("notes/hello.txt", "saved at " .. os.date())
print(readfile("notes/hello.txt"))
for _, name in ipairs(listfiles("notes")) do
    print(name)
end

Unsafe API

Allow unsafe Lua API in the same card unlocks HTTP, WebSockets and the clipboard. Enabling it resets the VM. Only use it for scripts you trust. See Sandbox & Safety.

Limits

  • The VM may use up to 1 GB of Lua memory. Allocations past that fail with a Lua memory error.
  • Scripts are text only. Precompiled bytecode is rejected.
  • An infinite loop without task.wait blocks the VM. Press Reset VM to stop it.

Next steps

Core ConceptsHow the clover VM is built and reset, how it schedules your code, and how it reports output and errors.Handling InputHotkeys, held keys and bound actions.Building a MenuBuild a crosshair settings window with toggles, sliders, a dropdown, a keybind, a color picker and configs.

Handling Input

Hotkeys, held keys and bound actions.

Input events only fire while Roblox is focused, and only for keys with a VK code in the KeyCode table.

Step by step

  1. Listen for a key press

    UserInputService.InputBegan passes an input object and false. Compare input.KeyCode with an Enum.KeyCode item. Mouse buttons arrive with KeyCode.Unknown and a UserInputType of MouseButton1, MouseButton2 or MouseButton3.

    lua
    UserInputService.InputBegan:Connect(function(input)
        if input.KeyCode == Enum.KeyCode.F then
            print("F pressed at", input.Position.X, input.Position.Y)
        elseif input.UserInputType == Enum.UserInputType.MouseButton2 then
            print("right click")
        end
    end)
  2. Toggle a feature with a hotkey

    Keep the state in a local and flip it on each press. notify shows a toast in the clover menu; its second argument is "success", "warning" or "error".

    lua
    local enabled = false
    
    UserInputService.InputBegan:Connect(function(input)
        if input.KeyCode ~= Enum.KeyCode.Insert then
            return
        end
        enabled = not enabled
        notify(enabled and "Feature on" or "Feature off", enabled and "success" or "warning")
    end)
  3. Hold a key

    Pair InputBegan with InputEnded, or poll UserInputService:IsKeyDown every tick. IsKeyDown returns false whenever Roblox is not focused.

    lua
    local holding = false
    
    UserInputService.InputBegan:Connect(function(input)
        if input.KeyCode == Enum.KeyCode.LeftAlt then
            holding = true
        end
    end)
    
    UserInputService.InputEnded:Connect(function(input)
        if input.KeyCode == Enum.KeyCode.LeftAlt then
            holding = false
        end
    end)
    
    RunService.Heartbeat:Connect(function()
        if UserInputService:IsKeyDown(Enum.KeyCode.LeftShift) then
            print("shift is down")
        end
    end)
  4. Bind an action to several inputs

    ContextActionService:BindAction calls your function with the action name, the input state and the input object, for every listed KeyCode or UserInputType. Binding the same name again replaces the old binding. It is not a global; get it with game:GetService.

    lua
    local ContextActionService = game:GetService("ContextActionService")
    
    ContextActionService:BindAction("Dash", function(name, state, input)
        if state == Enum.UserInputState.Begin then
            print(name, "via", tostring(input.KeyCode))
        end
    end, false, Enum.KeyCode.Q, Enum.UserInputType.MouseButton3)
  5. Clean up

    Connect returns a connection. Call Disconnect when you no longer need it, and UnbindAction for bound actions. A Reset VM removes everything at once.

    lua
    local connection = UserInputService.InputBegan:Connect(function(input)
        print(input.KeyCode.Name)
    end)
    
    task.delay(10, function()
        connection:Disconnect()
        game:GetService("ContextActionService"):UnbindAction("Dash")
        print("stopped listening")
    end)

Full example

Press Insert to turn a crosshair on and off, and hold Right mouse to make it bigger.

hotkey-crosshair.lua
local enabled = true
local aiming = false
local color = { R = 1, G = 0.8, B = 0.2, A = 1 }

UserInputService.InputBegan:Connect(function(input)
    if input.KeyCode == Enum.KeyCode.Insert then
        enabled = not enabled
        notify(enabled and "Crosshair on" or "Crosshair off")
    elseif input.UserInputType == Enum.UserInputType.MouseButton2 then
        aiming = true
    end
end)

UserInputService.InputEnded:Connect(function(input)
    if input.UserInputType == Enum.UserInputType.MouseButton2 then
        aiming = false
    end
end)

cheat.register("onDraw", function()
    if not enabled then
        return
    end
    local w, h = clover.screen_size()
    if w == 0 then
        return
    end
    local size = aiming and 14 or 6
    draw.Line(w / 2 - size, h / 2, w / 2 + size, h / 2, color, 2)
    draw.Line(w / 2, h / 2 - size, w / 2, h / 2 + size, color, 2)
end)

Things to know

  • Running a script twice connects its handlers twice. Press Reset VM before re-running.
  • An error inside a signal handler is logged and that handler is disconnected, so it never runs again. Wrap risky code in pcall if the handler must survive.
  • InputChanged, TextBoxFocused and the window focus signals exist but never fire.
  • For keybinds the user can change in a menu, use AddKeybind from the ui library instead.
ServicesLua versions of RunService, UserInputService, TweenService, ContextActionService, StarterGui and HttpService.SignalsEvent objects with Connect, Once, Wait and Fire, and every signal the engine exposes.EnumRoblox-style enumerations used by input, RunService and TweenService.

Building a Menu

Build a crosshair settings window with toggles, sliders, a dropdown, a keybind, a color picker and configs.

This guide uses the high-level ui library, which is the global Clover. Read Setting Up first if you have not run a script yet.

Reset before re-running

Windows and render callbacks live until the VM resets. Press Reset VM before running a new version of this script, otherwise you get two windows and two crosshairs.

Step by step

  1. Keep the settings in one table

    Element callbacks write into this table and the render callback reads from it, so the two never have to know about each other. Colors are plain { R, G, B } tables from 0 to 1, which is exactly what draw expects.

    lua
    local settings = {
        enabled = true,
        visible = true,
        size = 8,
        thickness = 2,
        style = "Cross",
        color = { R = 1, G = 0.8, B = 0.2 },
    }
  2. Create the window, a tab and a section

    Size must be a plain table such as { X = 420, Y = 380 }. A Vector2 is userdata and is ignored, so the window would fall back to 580 by 460.

    lua
    local Window = Clover:CreateWindow({
        Title = "Crosshair",
        SubTitle = "clover docs example",
        Size = { X = 420, Y = 380 },
    })
    local Main = Window:AddTab({ Title = "Main" })
    local Look = Main:AddSection("Look")
  3. Add a toggle and two sliders

    Every element takes one options table. Give it an Id so SaveConfig can store it. Callback runs on the script thread after the user changes the value. Rounding is the number of decimals the slider keeps.

    lua
    Look:AddToggle({
        Id = "CrosshairEnabled",
        Title = "Enabled",
        Default = settings.enabled,
        Callback = function(on)
            settings.enabled = on
        end,
    })
    
    Look:AddSlider({
        Id = "CrosshairSize",
        Title = "Size",
        Min = 2,
        Max = 30,
        Default = settings.size,
        Rounding = 0,
        Callback = function(value)
            settings.size = value
        end,
    })
    
    Look:AddSlider({
        Id = "CrosshairThickness",
        Title = "Thickness",
        Min = 1,
        Max = 6,
        Default = settings.thickness,
        Rounding = 1,
        Callback = function(value)
            settings.thickness = value
        end,
    })
  4. Add a dropdown and a color picker

    A single-choice dropdown passes the chosen option string. The color picker passes a { R, G, B } table. Set the dropdown's Default to its first option: the handle's cached Value always starts at the first option, whatever Default says.

    lua
    Look:AddDropdown({
        Id = "CrosshairStyle",
        Title = "Style",
        Options = { "Cross", "Dot", "Both" },
        Default = "Cross",
        Callback = function(choice)
            settings.style = choice
        end,
    })
    
    Look:AddColorpicker({
        Id = "CrosshairColor",
        Title = "Color",
        Default = settings.color,
        Callback = function(color)
            settings.color = color
        end,
    })
  5. Add a keybind

    In the default Toggle mode the callback receives true or false every time the bound key is pressed. Use your own variable for the state; Keybind:GetState() only reports the latch in Always mode. Key names are A-Z, 0-9, F1-F12, Space, Enter, Insert, LeftShift, RightShift and similar.

    lua
    local Binds = Main:AddSection("Binds")
    Binds:AddKeybind({
        Title = "Hide crosshair",
        Default = "X",
        Mode = "Toggle",
        Callback = function(toggled)
            settings.visible = not toggled
        end,
    })
  6. Save and load configs

    SaveConfig writes every element with an Id to C:\clover\workspace\configs\<name>.cfg. LoadConfig sets the values back but does not call the callbacks, so copy the loaded values into your settings table yourself.

    lua
    local Configs = Main:AddSection("Config")
    
    Configs:AddButton({
        Title = "Save",
        Callback = function()
            if Clover:SaveConfig("crosshair") then
                notify("Crosshair saved")
            end
        end,
    })
    
    Configs:AddButton({
        Title = "Load",
        Callback = function()
            if not Clover:LoadConfig("crosshair") then
                notify("No saved crosshair yet", "warning")
                return
            end
            local o = Clover.Options
            settings.enabled = o.CrosshairEnabled.Value
            settings.size = o.CrosshairSize.Value
            settings.thickness = o.CrosshairThickness.Value
            settings.style = o.CrosshairStyle.Value
            settings.color = o.CrosshairColor.Value
        end,
    })
  7. Draw from the settings

    The render callback runs every pass and only reads the settings table. clover.screen_size returns 0, 0 until the game view is known, so skip drawing until then.

    lua
    cheat.register("onDraw", function()
        if not settings.enabled or not settings.visible then
            return
        end
        local w, h = clover.screen_size()
        if w == 0 then
            return
        end
        local cx, cy = w / 2, h / 2
        local s = settings.size
        if settings.style ~= "Dot" then
            draw.Line(cx - s, cy, cx + s, cy, settings.color, settings.thickness)
            draw.Line(cx, cy - s, cx, cy + s, settings.color, settings.thickness)
        end
        if settings.style ~= "Cross" then
            draw.CircleFilled(cx, cy, settings.thickness + 1, settings.color, 16)
        end
    end)

Things to know

  • Element callbacks run on the script thread during its next pass and cannot yield. Wrap slow work in task.spawn.
  • SetValue and LoadConfig never fire Callback. Toggles, sliders and inputs move on screen; dropdowns, keybinds and color pickers only update their cached Value.
  • While a script window exists the overlay takes the mouse and keyboard. Call Window:Destroy() or Clover:Destroy() to give them back to the game.
  • Errors inside callbacks are caught and logged as ui callback: ... warnings instead of stopping the script.

Where to go next

uiBuild windows with tabs, sections and elements, with change callbacks and saved configs.clover.ui (raw)The low-level C functions behind the ui library. Everything is addressed by integer id.drawImmediate-mode 2D drawing that you repeat every frame, plus world-to-screen projection.

MCP Server

Let AI agents on your PC run Lua, edit script tabs, browse the game and read memory.

Full access

The token gives full access, including turning on the unsafe API without a prompt. Only share it with agents you trust and stop the server when done.

Starting the server

  1. Open the MCP panel

    Open the Lua VM window and pick MCP in the side panel.

  2. Start it

    Click Start server and confirm. clover listens on 127.0.0.1 on a random free port and logs MCP server listening on 127.0.0.1:<port> to the console. A new random token is made every time the server starts.

  3. Connect your agent

    Use Copy config for clients that read an mcpServers JSON block, or Copy command for Claude Code. Copy URL and Copy token copy the two parts on their own.

  4. Stop it

    Click Stop server. Open connections are closed and the token stops working.

Copy config
{
  "mcpServers": {
    "clover": {
      "type": "http",
      "url": "http://127.0.0.1:<port>/mcp",
      "headers": { "Authorization": "Bearer <token>" }
    }
  }
}
Copy command
claude mcp add --transport http clover http://127.0.0.1:<port>/mcp --header "Authorization: Bearer <token>"

Connection rules

RuleBehavior
AddressLoopback only. Connections from any other address are closed immediately.
EndpointPOST /mcp with JSON-RPC. Other paths return 404 and other methods return 405.
AuthAuthorization: Bearer <token> is required, otherwise 401.
BrowsersRequests carrying Origin or Sec-Fetch-* headers are rejected with 403, so web pages cannot reach the server. A wrong Host header is also rejected.
Limits8 connections at once (503 after that), 8 MB request bodies (413), 30 second request deadline (408). Chunked uploads are refused; send Content-Length.
Methodsinitialize, ping, tools/list and tools/call.

Tools

Lua VM

ToolArgumentsWhat it does
lua_executecode (required), timeout_ms (100 to 120000, default 10000)Runs a chunk and returns its return values, everything it printed and any error with a traceback. Scripts that yield keep running in the background after the call returns.
lua_vm_guidenoneReturns a written guide to the VM, its APIs and its limits. The same text is sent as the server instructions on initialize.
lua_get_logsince, limit (1 to 1000, default 100), include_robloxReads the VM log. Pass the returned next_since back as since to read only new lines.
lua_clear_lognoneClears the log.
lua_resetnoneResets the VM like the Reset VM button and waits up to 10 seconds for it to finish.
lua_statusnoneWhether the VM runs, whether the unsafe API is on, Lua memory in use and the log sequence.
lua_set_unsafe_apienabled (required)Turns the unsafe API on or off. Resets the VM.

Editor

ToolArgumentsWhat it does
editor_list_tabsnoneLists the tabs open in the Lua VM window. The window has to be rendering for tabs to be visible.
editor_read_tabname or index (1-based)Reads a tab's code. Without arguments it reads the active tab.
editor_write_tabname, code (both required)Creates a tab or replaces the code of the tab with that name. Tabs hold up to 32767 bytes.

Workspace

ToolArgumentsWhat it does
workspace_listpathLists a folder in the workspace. Empty lists the root.
workspace_readpath (required)Reads a text file.
workspace_writepath, content (both required)Writes a file, creating folders as needed. Up to 16 MB.
workspace_deletepath (required)Deletes a file.

Game and memory

ToolArgumentsWhat it does
game_infononePlaceId, GameId, JobId and the local player.
instance_treepath (dot separated, empty for the DataModel), depth (default 2), max_nodes (default 200)Prints the Instance tree under a path such as Workspace.Map.
read_game_scriptpath (required)Decompiles a LocalScript or ModuleScript by Instance path and returns the source. See Scripts.
memory_statusnoneWhether clover is attached to Roblox, with the process id and module base.
memory_readaddress (required, decimal or 0x hex string), format, size, countReads Roblox process memory. format is hex (default, up to 65536 bytes), cstring, or a typed value (u8 i8 u16 i16 u32 i32 u64 i64 f32 f64 pointer) repeated count times.

Remote spy

ToolArgumentsWhat it does
remote_spy_statusnoneWhether the spy and its hook are active, plus fire and remote counts.
remote_spy_set_enabledenabled (required)Turns the remote spy on or off. Turning it on hooks RemoteEvents in the Roblox process and shows the spy window.
remote_spy_listsort (count, recent or name), filter, limit, include_luaLists RemoteEvents the client fired: name, class, path, count and age. Arguments are not captured. include_lua adds a snippet that fires the remote. See Remotes.
remote_spy_clearnoneForgets every recorded remote.

Globals

Every global name and where it's documented, plus the small global functions.

One shared global table

All scripts share one global table. getgenv() and _G are that table, and values persist until the VM resets.

Where functions live

Most engine functions live on the clover table and are re-exported as draw.*, utility.*, file.* and so on.

clover.error does not throw

clover.error(...) only writes a line to the log. Use Lua's global error(...) to raise an error and stop the current script.

globals-tour.lua
local name, version = identifyexecutor()
print("executor:", name, version)
print("place:", clover.getplaceid(), clover.getgamename())
print(typeof(Vector3.new(1, 2, 3)), typeof(game), typeof({}))

getgenv().TourRuns = (getgenv().TourRuns or 0) + 1
notify("Tour ran " .. TourRuns .. " time(s)", "success")
clover.warn("clover.warn joins", "with spaces")

Global index

Names are grouped by origin. Each row links to the page that documents it.

Lua standard library

NameWhat it isPage
assert, collectgarbage, error, getmetatable, ipairs, next, pairs, pcall, rawequal, rawlen, rawset, select, setmetatable, tonumber, tostring, type, xpcall, _G, _VERSIONStandard Lua 5.4 base functions and values, unchanged.stdlib
coroutine, table, string, math, utf8Standard libraries. table, math and string gain Luau-style extras such as table.find, math.clamp and string.split.stdlib
print, warnReplaced by clover versions that write to the clover log.print, warn
rawgetStandard rawget, wrapped so it can also read string keys from game tables returned by getgc.gc
load, loadstringCompile a Lua source string. Only text chunks are accepted.sandbox
osReduced to os.clock, os.time, os.date and os.difftime.sandbox
debugThe full debug library only when the unsafe API is on; otherwise a table with just getupvalue, getupvalues, setupvalue and getinfo.gc, sandbox
io, package, requireNot available (nil).sandbox
dofile, loadfileRemoved from the base library, then redefined to load files from the workspace folder.compat
unpackThe same function as table.unpack.stdlib
sharedAn empty table created at VM start for passing values between scripts.stdlib
time, elapsedTimeSeconds since the VM started, measured with os.clock. Both names are the same function.stdlib
gcinfoLua memory in use, in kilobytes (collectgarbage("count")).stdlib
RandomRandom.new([seed]) pseudo-random generator with NextNumber, NextInteger, NextUnitVector, Shuffle and Clone.stdlib

Roblox-style API

NameWhat it isPage
gameThe DataModel as an Instance, or nil while no game is attached. Replaced when the DataModel changes.game
workspaceThe Workspace Instance, or nil.game
InstanceTable holding the Instance.new constructor.instance
Vector22D vector type.Vector2
Vector33D vector type.Vector3
Color3RGB color type.Color3
CFrameCoordinate frame type.CFrame
EnumEnum groups such as Enum.KeyCode, Enum.UserInputType and Enum.EasingStyle.Enum
RunService, UserInputService, TweenServiceService tables. game:GetService returns the same tables.services
TweenInfoTweenInfo.new(...) constructor for tweens.services
HttpServiceJSON and web helpers. game:GetService("HttpService") returns the same table.services, http
taskScheduler library: task.wait, task.spawn, task.delay, task.defer, task.cancel.task
wait, spawn, delay, Delay, Spawn, WaitGlobal scheduler shortcuts.task
Drawing, cleardrawcacheRetained drawing objects (Drawing.new, Drawing.clear) and a global that removes them all.Drawing

clover namespaces

NameWhat it isPage
cloverThe raw C API table. Every field is listed in The clover table.this page
CloverThe high-level UI library (Clover:CreateWindow, configs, options).ui
drawImmediate-mode drawing names for the clover.draw_* functions.draw
utilityRandom numbers, tick count, delta time, mouse position, menu state and clipboard.utility
keyboard, mouseKey state, synthetic key presses and mouse input.input
cheatcheat.register events and cheat.GetWindowSize.cheat
fileWorkspace file helpers (file.Read, file.Write, file.Exists, file.Delete).file
cryptcrypt.hash, crypt.random and base64 helpers.crypt
bit3232-bit bitwise operations.bit32
WebSocketWebSocket.connect(url).WebSocket

Executor-style functions

NameWhat it isPage
getgenv, identifyexecutor, notify, newproxy, typeof, tickSmall helpers documented below.Functions
WorldToScreen, world_to_screenProject a world position to screen pixels.draw
getmousepos, iskeypressed, isrbxactiveMouse position, key state and Roblox window focus.input
readfile, writefile, appendfile, isfile, isfolder, makefolder, delfile, listfilesFile functions confined to the clover workspace folder.file
base64encode, base64decodeBase64 encoding.crypt
request, http_request, HttpGetHTTP requests. They need the unsafe API.http
fireserver, invokeserver, firesignal, fireproximitypromptFire remotes, signals and proximity prompts.remotes
getscripts, getscriptbytecode, decompile, getsenvScript discovery and decompilation.scripts
getgc, findgc, getclosures, getrawkeys, setgc, gcprobe, getgc_infoInspect the game's Luau heap.gc
getfflag, setfflag, reloadskyFast flags and sky reload.fflags
queue_on_teleport, queueonteleport, clear_teleport_queueRun code again after a teleport.teleport
getrawmetatable, setrawmetatable, compareinstances, islclosure, iscclosure, getexecutornameCompatibility shims.compat
rconsoleprint, rconsoleinfo, rconsolewarn, rconsoleerr, rconsoleclear, rconsolename, rconsolesettitle, rconsoleinput, printconsoleConsole shims that write to the clover log.compat
setfpscap, getfpscap, fireclickdetector, gethiddenproperty, sethiddenproperty, getinstancesCompatibility shims, some of them approximations.compat
base64, base64_encode, base64_decodeAliases of base64encode and base64decode.compat, crypt

Names that are not globals

  • Library is a local inside the UI prelude. Use the global Clover (local Library = Clover). See ui.
  • getgamename and getplaceid exist only as clover.getgamename and clover.getplaceid.
  • The memory functions exist only on the clover table (clover.memory_read, clover.read_int, ...). See memory.
  • setclipboard and toclipboard are not globals. Use utility.SetClipboard or clover.setclipboard. See utility.
  • ContextActionService and StarterGui are only returned by game:GetService. See services.

Functions

print(...)

Converts every argument with tostring rules (so __tostring and __name apply), joins them with a tab and writes one line to the clover log at info level. It replaces Lua's built-in print.

Parameters:

  • ...: any - Values to print. With no arguments an empty line is logged.
  • The global print tags lines as vm; clover.print tags them as lua.
  • printconsole is the same function. See compat.
  • The log keeps the most recent 4096 entries.
lua
print("health", 100, true)
print(Vector3.new(1, 2, 3))

warn(...)

Same as print but logs at warning severity. It replaces Lua 5.4's warn: control messages such as "@on" and "@off" are not special and are logged like any other text.

Parameters:

  • ...: any - Values to log, joined with a tab.
lua
local camera = workspace and workspace.CurrentCamera
if not camera then
    warn("no camera yet")
end

notify(text, [status])

Shows a toast notification in the clover overlay, titled "Clover", with text as the message. Returns nothing.

Parameters:

  • text: string - The message. Numbers are converted to strings; other types raise an error.
  • status: string (optional, default "success") - "success", "warning" or "error" picks the style. Any other value shows a success notification.

Aliases: clover.notify

  • Clover:Notify and StarterGui:SetCore("SendNotification", ...) both end up calling this function.
lua
notify("ESP enabled")
notify("Target lost", "warning")

identifyexecutor()

Returns the executor name and version.

Returns:

  • name: string - Always "clover".
  • version: string - Always "1.0".
lua
local name, version = identifyexecutor()
print(name, version)

getgenv()

Returns the shared global table, the same as _G.

Returns:

  • table - The global table.

Aliases: clover.getgenv

  • There is no separate game environment; getrenv does not exist.
  • Values survive between script runs and are cleared only when the VM resets.
lua
if getgenv().MyEspLoaded then
    print("already running")
    return
end
getgenv().MyEspLoaded = true
print(MyEspLoaded, getgenv() == _G)

typeof(value)

Like type, but engine userdata report their type name, such as Vector3.

Parameters:

  • value: any - The value to inspect. It is required: typeof() with no argument raises an error, while typeof(nil) returns "nil".

Returns:

  • string - The type name. See the table below.

Aliases: clover.typeof

lua
print(typeof(Vector3.new()), typeof(Color3.new(1, 0, 0)), typeof(workspace))
print(typeof(42), typeof("x"), typeof(newproxy(true)))
Valuetypeof result
Vector2, Vector3, Color3, CFrame values"Vector2", "Vector3", "Color3", "CFrame"
Instances (game, workspace, children, Instance.new results)"Instance"
Objects from Drawing.new"Drawing"
Handles returned by task.delay and task.defer"Task"
Connections returned by engine signals such as RunService.Heartbeat:Connect"RBXScriptConnection"
Signals, Enum items, TweenInfo values, Random objects, WebSocket objects and the table and function proxies returned by getgc"table" (they are tables in clover)
newproxy results and other userdata"userdata"
Everything elseSame as type

tick()

Returns the current Unix time (seconds since 1970-01-01 UTC) from the system clock, as a float with sub-second precision.

Returns:

  • number - Seconds since the Unix epoch.

Aliases: clover.tick

  • time() returns seconds since the VM started instead. See stdlib.
lua
local started = tick()
task.wait(1)
print(string.format("waited %.2f s", tick() - started))

newproxy([base])

Creates an empty userdata (zero bytes, no user values). Useful as a unique key or as an object whose behaviour comes entirely from its metatable.

Parameters:

  • base: any (optional) - A userdata copies its metatable, true gives a new empty metatable, and nil/false gives none.

Returns:

  • userdata - The new proxy. Read its metatable with getmetatable.
lua
local proxy = newproxy(true)
local mt = getmetatable(proxy)
mt.__tostring = function() return "my proxy" end
mt.__index = function(_, key) return key .. "!" end
print(tostring(proxy), proxy.hello, typeof(proxy))

clover-only functions

These live only on the clover table. The clover logging functions differ from the globals of the same name: they join arguments with a single space and tag lines as script output (lua).

clover.print(...)

Converts the arguments with tostring rules, joins them with a single space and writes one info line to the clover log.

Parameters:

  • ...: any - Values to log.

Aliases: clover.printl

lua
clover.print("loaded", 3, "modules")

clover.warn(...)

Like clover.print but logs at warning severity.

Parameters:

  • ...: any - Values to log.
lua
clover.warn("config missing, using defaults")

clover.error(...)

Like clover.print but logs at error severity. It does not raise an error and the script keeps running.

Parameters:

  • ...: any - Values to log.

Aliases: clover.errorl

lua
local ok, problem = pcall(function() error("boom") end)
if not ok then
    clover.error("caught:", problem)
end
print("still running")

clover.getgamename()

Returns the Name of the current DataModel, the same text game.Name gives.

Returns:

  • string - The name, or an empty string when Roblox is not attached or no DataModel is found.
lua
local name = clover.getgamename()
if name ~= "" then
    print("in", name)
end

clover.getplaceid()

Returns the PlaceId of the current game, read from the DataModel. game.PlaceId reads the same field.

Returns:

  • integer - The PlaceId, or 0 when Roblox is not attached or no DataModel is found.
lua
local placeId = clover.getplaceid()
if placeId == 0 then
    print("not in a game yet")
else
    print("place", placeId)
end

The clover table

clover holds the raw engine functions. draw, utility, keyboard, mouse, cheat and file are built from it. clover.ui is the low-level UI table.

FieldsOther namesDocumented on
print, printl, warn, error, errorlNone (the globals print and warn are separate functions)this page
notify, getgenv, tick, typeofGlobals of the same namethis page
getgamename, getplaceidNonethis page
memory_read, memory_write, read_int, read_int64, read_float, read_pointer, read_string, module_base, getbaseNonememory
is_key_down, iskeypressed, keypress, keyreleasekeyboard.*input
ismouse1pressed, ismouse2pressed, mouse1press, mouse1release, mouse1click, mouse2press, mouse2release, mouse2click, mousemoveabs, mousemoverel, mousescrollmouse.*input
isrbxactive, setrobloxinputNone (the global isrbxactive is a separate function that does the same check)input
setclipboard, utility_get_clipboardutility.SetClipboard, utility.GetClipboardutility
utility_random_int, utility_random_float, utility_get_tick_count, utility_get_delta_time, utility_get_mouse_pos, utility_get_menu_stateutility.*utility
screen_size, world_to_screen, WorldToScreenGlobals world_to_screen and WorldToScreen for the last two; none for screen_sizedraw
draw_box, draw_line, draw_circle, draw_text, draw_rect, draw_rect_filled, draw_circle_filled, draw_triangle, draw_triangle_filled, draw_gradient, draw_text_outlined, draw_polyline, draw_get_text_sizedraw.* (all except draw_box)draw
players, local_playerNoneplayers
cheat_register, cheat_get_window_size, on_tick, on_rendercheat.Register and cheat.GetWindowSize (with their aliases) for the first two; none for on_tick and on_rendercheat
file_read, file_write, file_exists, file_deletefile.*file
readfile, writefile, appendfile, isfile, isfolder, makefolder, delfile, listfilesGlobals of the same namefile
base64encode, base64decodeGlobals of the same name, crypt.base64encode, crypt.base64decodecrypt
uiNoneclover.ui (raw)

The Clover global

The UI prelude stores its library table in _G.Clover. It is the entry point for menus; see ui for the full API. Its top-level fields:

FieldTypeDescription
Clover.Loadedbooleantrue until Clover:Destroy() runs.
Clover.Unloadedbooleanfalse until Clover:Destroy() runs.
Clover.Versionstring"1.0.0".
Clover.OptionstableElement handles stored by their Id option.
Clover:CreateWindow(opts)methodCreates a window and returns its handle.
Clover:Notify(opts)methodCalls notify with opts.Content (or opts.Title) and opts.Severity.
Clover:SafeCallback(fn, ...)methodCalls fn in pcall and logs a failure with clover.warn.
Clover:Destroy()methodRemoves every window and sets Loaded to false.
Clover:SaveConfig(name), Clover:LoadConfig(name)methodSave and load Clover.Options values in configs/<name>.cfg in the workspace.
Clover:Minimize(), Clover:Maximize()methodDo nothing; use the window's own methods.

game

Handles to the Roblox DataModel and Workspace, plus GetService and IsLoaded.

game can be nil

game and workspace are nil until the engine finds a DataModel in the attached Roblox process. Check them before indexing, or your script stops with attempt to index a nil value.

Refreshed every tick

game and workspace are reassigned when the DataModel changes (teleport, rejoin). Don't cache them in locals.

Everything here is an Instance

game, workspace and services are Instance values. game.Players works like game:GetService("Players").

game-info.lua
if not game then
    warn("Roblox is not loaded yet")
    return
end

local Players = game:GetService("Players")
print("place", game.PlaceId, "universe", game.GameId, "server", game.JobId)
print("players online:", #Players:GetPlayers())

local me = Players.LocalPlayer
if me then
    print("playing as", me.Name, me.UserId)
end

Players.PlayerAdded:Connect(function(player)
    print(player.Name .. " joined")
end)

Players.PlayerRemoving:Connect(function(player)
    print(player.Name .. " left")
end)

Globals

game

The DataModel as an Instance, or nil when no game is loaded. Updated automatically when the game changes.

  • game:GetFullName() returns an empty string, because full names stop at the DataModel.
  • Assigning your own value to the global game works until the DataModel address changes, then the engine overwrites it.

workspace

The Workspace as an Instance, or nil. Updated together with game.

  • There is no capitalised Workspace global; use workspace, game.Workspace or game:GetService("Workspace").

DataModel methods

game:GetService(name)

Returns a service by name, checking three sources in order (see the table below): the engine's Lua shim tables, the global service tables, then the children of the instance it is called on.

Parameters:

  • name: string - Service name. Numbers are converted to strings; any other type raises an error.

Returns:

  • Instance | table | nil - The service Instance, one of the Lua service tables, or nil when nothing matches. It never raises an error for an unknown name.
  • GetService is registered on every Instance. On anything other than game, step 3 searches that instance's own children.
  • Services are not created on demand. A service that is not a child of the DataModel resolves to nil.
lua
local Players = game:GetService("Players")
local Lighting = game:GetService("Lighting")
local RunService = game:GetService("RunService")

if Lighting then
    RunService.Heartbeat:Connect(function()
        Lighting.ClockTime = 12
    end)
end
print(#Players:GetPlayers(), "players")
OrderName passedResult
1ContextActionService, StarterGui (any letter case)The engine's Lua shim table for that service (see Services).
2RunService, UserInputService, TweenService, HttpServiceThe global table of the same name. The name must use this exact casing: another casing matches this step but returns nil.
3Anything elseThe first child whose Name or ClassName equals name exactly (case-sensitive), such as Players, Lighting, ReplicatedStorage, Workspace or CoreGui. nil when no child matches.

game:IsLoaded()

Returns true when the engine currently resolves a DataModel in the Roblox process, false otherwise.

Returns:

  • boolean - Whether a DataModel address is available right now.
  • It ignores the instance it is called on and does not wait for the place to finish loading. It only reports whether the DataModel pointer resolves.
  • Registered on every Instance, so workspace:IsLoaded() gives the same answer.

game:HttpGet(url)

Same as the global HttpGet. Returns the response body.

Requires the unsafe API.

Parameters:

  • url: string - Full http or https URL.

Returns:

  • string - The response body for a 2xx status.

Aliases: game:HttpGetAsync(url), HttpGet(url)

  • Raises HTTP access requires Allow unsafe Lua API. when the unsafe API toggle is off (see Sandbox).
  • Raises HttpGet failed: HTTP <status> for a non-2xx status and HttpGet failed: <reason> when the request itself fails.
  • The request runs synchronously on the script thread. See http for limits and the other request functions.

DataModel properties

game.PlaceId

The current place id, read as a 64-bit integer from the DataModel.

Read-only.

Returns:

  • integer - Place id.

game.GameId

The universe (experience) id, read as a 64-bit integer from the DataModel.

Read-only.

Returns:

  • integer - Universe id.

game.JobId

The server job id string, or an empty string when its pointer is not readable.

Read-only.

Returns:

  • string - Job id.

game.Workspace

The Workspace Instance, resolved the same way as the workspace global, or nil.

Read-only.

Returns:

  • Instance | nil - Workspace.

Aliases: game.workspace

game.Loaded

Compatibility stand-in: the game counts as already loaded, so Connect and Once run the callback right away and Wait returns immediately.

  • This is a plain Lua table, not an engine signal; it has no Fire method.
lua
if not game:IsLoaded() then
    game.Loaded:Wait()
end
print("ready in place", game.PlaceId)

Players service

game:GetService("Players") returns the real Players Instance. These members are available when the instance's ClassName is exactly Players. For a lighter, pre-read list of players with positions and health, see players.

Players.LocalPlayer

The local Player Instance, or nil when its pointer is not valid (for example while joining).

Read-only.

Returns:

  • Instance | nil - The local Player.

Players:GetPlayers()

Returns an array of the instance's children whose ClassName is Player, in child order.

Returns:

  • {Instance} - Array of Player instances. Empty when there are none.
  • Call it on the Players service. game:GetPlayers() returns an empty table.
lua
for _, player in ipairs(game:GetService("Players"):GetPlayers()) do
    local character = player.Character
    print(player.Name, player.DisplayName, character ~= nil)
end

Players.PlayerAdded

Fires with the new Player when someone joins. Players already in the server when the engine starts watching don't fire.

Parameters:

  • player: Instance - Passed to the callback: the Player that joined.
  • The connection is not tied to one Players instance: every PlayerAdded connection receives every join.
  • Returns an engine signal with Connect, Once, Wait and Fire.

Players.PlayerRemoving

Fires with the Player Instance when a previously seen player is no longer a child of the Players service. It is detected after the player is gone, so properties read from it may already be empty.

Parameters:

  • player: Instance - Passed to the callback: the Player that left.
  • Like PlayerAdded, every connection receives every leave. Returns an engine signal.

Player instances also have Character, CharacterAdded, DisplayName, UserId and Team; see Instance class properties and Player.CharacterAdded.

Workspace properties

workspace.CurrentCamera

The current Camera Instance, or nil. Available when the instance's ClassName is exactly Workspace.

Read-only.

Returns:

  • Instance | nil - The Camera. Its FieldOfView is readable and writable (see Instance).

workspace.DistributedGameTime

Seconds of game time since the server started, read as a double.

Read-only.

Returns:

  • number - Game time in seconds.

clover helpers

clover.getgamename()

Returns the DataModel's Name without going through the game global.

Returns:

  • string - The DataModel name, or an empty string when there is no DataModel or the name is empty.
  • Only available on the clover table; there is no global getgamename.

clover.getplaceid()

Returns the current place id without going through the game global.

Returns:

  • integer - The place id, or 0 when there is no DataModel.
  • Only available on the clover table; there is no global getplaceid.

Instance

A handle to a live Roblox object: reading a property reads game memory.

Handles, not objects

Every lookup creates a new handle. Handles to the same object are == but not the same table key; key tables by inst.Address. Removed objects aren't tracked.

Only the listed properties exist

Unknown keys look up a child with that name. Writing an unsupported property is silently ignored.

Writes go straight into game memory

Writes only patch this client's memory and the game may overwrite them. Setting Parent and Instance.new block for up to 3 s each.

instance-basics.lua
local player = game:GetService("Players").LocalPlayer
local character = player and player.Character
if not character then
    return
end

local humanoid = character:FindFirstChildOfClass("Humanoid")
local root = character:FindFirstChild("HumanoidRootPart")
if humanoid and root then
    print(root:GetFullName(), humanoid.Health, root.Position)
    humanoid.WalkSpeed = 24
end

for _, child in ipairs(character:GetChildren()) do
    if child:IsA("BasePart") then
        child.Transparency = 0.5
    end
end

character.ChildAdded:Connect(function(child)
    print("added", child.Name, child.ClassName)
end)

Constructor

Instance.new(className, [parent])

Creates a real object in Roblox, such as a Part or Folder, and returns it.

Parameters:

  • className: string - Exact class name, case-sensitive.
  • parent: Instance (optional, default nil) - Parent to set after creation. Must be an Instance or nil.

Returns:

  • Instance - The new object.
  • Failure raises Instance.new failed (<code>); the codes are listed below.
  • Fails with code 2 on unsupported Roblox builds.
  • If setting parent fails (code 12) the object has already been created but no handle is returned.
  • Name cannot be written, so new objects keep their default name.
lua
local ok, part = pcall(Instance.new, "Part", workspace)
if ok then
    part.Anchored = true
    part.Size = Vector3.new(4, 1, 4)
    part.Position = Vector3.new(0, 50, 0)
    part.Color = Color3.fromRGB(255, 120, 200)
else
    warn(part)
end
CodeMeaning
1className is an empty string.
2Roblox is not attached, or the running build is not supported.
3The class name is not in the reflection registry, or it has no creator.
4, 5, 17The creator's function table could not be read or does not point at executable code.
6The creatability check did not run inside the game.
7The class exists but is not creatable.
8, 9The create call could not be prepared or did not run.
10, 11The game returned something that is not a valid object.
12The object was created but setting its parent failed.

Properties

Instance.Name

The object's name.

Read-only.

Returns:

  • string - The name, or an empty string when it cannot be read. Names longer than 255 bytes also read as an empty string.
  • Writing Name is ignored.

Instance.ClassName

The object's class name, read from its class descriptor.

Read-only.

Returns:

  • string - The class name, or an empty string when it cannot be read.

Instance.Address

The object's memory address. Use it as a table key or with the memory functions.

Read-only.

Returns:

  • integer - The raw address.
lua
local seen = {}
for _, child in ipairs(workspace:GetChildren()) do
    seen[child.Address] = child.Name
end

Instance.Parent

The parent Instance, or nil when the parent pointer is not valid. Writing it reparents the object by calling the game's own SetParent function.

Returns:

  • Instance | nil - The parent.
  • Assign an Instance or nil. Any other type raises an error. inst.Parent = nil takes the object out of the tree; there is no Destroy method.
  • A failed reparent is silent.
  • The script thread waits for the engine call, up to 3 seconds.

Class-specific properties

Only available when ClassName matches exactly. BasePart covers Part, MeshPart, UnionOperation, TrussPart, WedgePart, CornerWedgePart, SpawnLocation, Seat and VehicleSeat. Parts without physics read zeros and ignore writes.

ClassPropertyTypeAccessNotes
BasePartPositionVector3read/write
BasePartSizeVector3read/write
BasePartCFrameCFrameread/writeWriting a Vector3 (or an X/Y/Z table) moves the part and keeps its rotation.
BasePartTransparencynumberread/write
BasePartAnchoredbooleanread/writeBit in the primitive's flags byte.
BasePartCanCollidebooleanread/writeBit in the primitive's flags byte.
BasePartVelocity, AssemblyLinearVelocityVector3read/writeSame value under two names.
BasePartAssemblyAngularVelocityVector3read
BasePartColor, Color3Color3read/writeSame value under two names.
HumanoidHealth, MaxHealthnumberread/write
HumanoidWalkSpeed, JumpPower, JumpHeightnumberread/write
HumanoidDisplayNamestringread
HumanoidRootPartInstance or nilreadThe HumanoidRootPart.
PlayerCharacterInstance or nilreadThe current character model.
PlayerDisplayNamestringread
PlayerUserIdintegerread
PlayerTeamInstance or nilread
PlayerCharacterAddedeventreadSee Player.CharacterAdded.
CameraFieldOfViewnumberread/writeDegrees. If the game stores the value in radians (between 0 and 3.2) it is converted to degrees on read and back to radians on write.
LightingBrightness, ClockTime, FogStart, FogEndnumberread/write
LightingGlobalShadowsbooleanread/write
LightingAmbient, OutdoorAmbient, FogColorColor3read/write
ToolTextureIdstringread
ToolToolTip, TooltipstringreadSame value under two names.
ToolEnabledbooleanread
BoolValueValuebooleanread/write
IntValueValueintegerread/writeStored as a 32-bit integer. Writing a number without an integer representation (such as 2.5) raises an error.
NumberValueValuenumberread/write
StringValueValuestringread/writeWrites accept strings and numbers.
ObjectValueValueInstance or nilread/writeWrites accept an Instance or nil; anything else raises an error.
Vector3ValueValueVector3read/write
BindableEventEventeventreadSee BindableEvent.Event.
RemoteEvent, UnreliableRemoteEventOnClientEventeventreadSee RemoteEvent.OnClientEvent.
ProximityPromptTriggeredeventreadSee ProximityPrompt.Triggered.
DataModelPlaceId, GameId, JobId, Workspace, workspace, Loaded, HttpGet, HttpGetAsyncvariousreadSee game.
PlayersLocalPlayer, PlayerAdded, PlayerRemovingvariousreadSee Players service.
WorkspaceCurrentCamera, DistributedGameTimevariousreadSee Workspace properties.

Value on other value classes (CFrameValue, Color3Value, ...) is not supported and falls through to the child lookup.

Value conversion

  • Floats stored by the game (Transparency, Health, Brightness, ...) read as Lua floats. Ids and integers (UserId, Address, IntValue.Value) read as Lua integers.
  • Strings are read from game memory and are limited to 255 bytes; longer or unreadable strings read as "".
  • number writes need a number or a numeric string. Anything else raises a number expected error.
  • boolean writes use Lua truthiness: nil and false clear the value, anything else sets it.
  • Vector3 writes accept a Vector3 or a table with X, Y, Z (or x, y, z) fields; missing fields count as 0. Other values are ignored.
  • Color3 writes accept a Color3 or a table with R, G, B (or r, g, b) fields, each 0 to 1. Other values are ignored.
  • CFrame writes accept a CFrame (position and rotation), or a Vector3 or table (position only).
  • After a write the engine handles, it fires that property's GetPropertyChangedSignal connections. For Parent this only happens when the reparent succeeded.

Indexing rules

inst.key is resolved in this order, and the first match wins:

  1. Methods on the Instance metatable (GetChildren, FindFirstChild, ...).
  2. If the stored address is not a plausible pointer, every other key reads nil.
  3. Name, ClassName, Address, Parent, ChildAdded, ChildRemoved, DescendantAdded.
  4. The class-specific properties for the object's ClassName.
  5. The first direct child whose Name or ClassName equals the key. character.Humanoid therefore also finds a renamed Humanoid when no child is literally named Humanoid before it.

Children with reserved names

A child named like a method or property (a part called Size, a folder called Fire) is shadowed by rule 1, 3 or 4. Use inst:FindFirstChild("Size") to reach it.

Methods

Instance:GetChildren()

Returns the direct children in the game's own child order. Each call builds a new table of new handles.

Returns:

  • {Instance} - Array of children. Empty when there are none or the address is not valid.
  • As a safety limit, an object with more than 10,000 children reports none at all.

Instance:GetDescendants()

Returns every descendant, depth first: each child is followed by its own descendants before the next sibling.

Returns:

  • {Instance} - Array of descendants, capped at 50,000 entries.
  • Walking a large tree such as game or workspace reads a lot of memory in one call. Prefer FindFirstChild chains or a narrower root when you can.
lua
local count = 0
for _, item in ipairs(workspace:GetDescendants()) do
    if item:IsA("BasePart") then
        count = count + 1
    end
end
print(count, "parts in workspace")

Instance:FindFirstChild(name)

Returns the first direct child whose Name or ClassName equals name exactly (case-sensitive), or nil.

Parameters:

  • name: string - Name (or class name) to look for.

Returns:

  • Instance | nil - The first match.
  • Only direct children are searched. A second recursive argument is accepted but ignored; loop over GetDescendants() for a deep search.
  • Matching ClassName as well as Name differs from Roblox: FindFirstChild("Humanoid") also returns a Humanoid that was renamed.

Instance:FindFirstChildOfClass(className)

Returns the first direct child whose ClassName equals className exactly, or nil. Inheritance is not considered, and names are not matched.

Parameters:

  • className: string - Exact class name.

Returns:

  • Instance | nil - The first child of that class.

Instance:FindFirstChildWhichIsA(className)

Returns the first direct child whose Name equals className or whose class passes IsA(className), or nil.

Parameters:

  • className: string - A class name or one of the class groups.

Returns:

  • Instance | nil - The first match.
lua
local player = game:GetService("Players").LocalPlayer
local character = player and player.Character
local part = character and character:FindFirstChildWhichIsA("BasePart")
print(part and part.Name)

Instance:WaitForChild(name, [timeout])

Returns the child, waiting (checking every 0.05 s) until it appears or timeout runs out.

Yields.

Parameters:

  • name: string - Name (or class name) to wait for. The same Name-or-ClassName match as FindFirstChild.
  • timeout: number (optional, default 5) - Seconds to wait. Negative values count as 0. With 0 a missing child returns nil right away without yielding.

Returns:

  • Instance | nil - The child, or nil on timeout. A timeout raises no error and logs no warning.
  • The timeout is counted in 0.05 second steps per check, so the real wait can run slightly longer than timeout.
  • Can't yield inside engine callbacks; call it from the script body or task.spawn.
lua
local player = game:GetService("Players").LocalPlayer
local gui = player:WaitForChild("PlayerGui", 10)
if gui then
    print(gui:GetFullName())
else
    warn("PlayerGui did not appear")
end

Instance:IsA(className)

Returns true when the object's ClassName equals className, when className is "Instance", or when className is a class group (table below) that contains the object's ClassName.

Parameters:

  • className: string - Class name or group name.

Returns:

  • boolean - Whether the object counts as that class.
  • There is no full class tree; unlisted abstract names only match an exact ClassName.

IsA class groups

classNameMatches these ClassNames
InstanceEvery object.
BasePart, PVInstancePart, MeshPart, BasePart, UnionOperation, TrussPart, WedgePart, CornerWedgePart, SpawnLocation, Seat, VehicleSeat (Model is not a PVInstance here).
GuiObject, GuiBase2d, GuiBaseGuiObject, Frame, TextLabel, TextButton, TextBox, ImageLabel, ImageButton, ScrollingFrame, ViewportFrame, CanvasGroup, VideoFrame.
ValueBaseBoolValue, IntValue, NumberValue, StringValue, ObjectValue, Vector3Value, CFrameValue, Color3Value, BrickColorValue, RayValue.
ModelModel, WorldModel, Actor.
LuaSourceContainerLocalScript, Script, ModuleScript.
AccoutrementAccoutrement, Hat, Accessory.
ToolTool, HopperBin.
LayerCollectorScreenGui, BillboardGui, SurfaceGui.
Anything elseOnly an exact ClassName match.

Instance:IsDescendantOf(ancestor)

Walks up the parent chain (at most 64 levels) and returns true if ancestor is one of the parents. An object is not a descendant of itself.

Parameters:

  • ancestor: Instance - The possible ancestor. Any non-Instance value returns false instead of raising an error.

Returns:

  • boolean - Whether ancestor is above this object.

Instance:GetFullName()

Returns the dot-separated path from the topmost ancestor below the DataModel down to this object, for example Workspace.Map.Door. The DataModel itself is never part of the path.

Returns:

  • string - The path. game:GetFullName() returns an empty string.
  • At most 64 levels are walked. For an object outside the DataModel the path starts at its topmost ancestor.

Instance:GetPropertyChangedSignal(property)

Returns a signal that fires when a clover script changes property on this object. Changes made by the game aren't seen.

Parameters:

  • property: string - Property (or attribute) name, case-sensitive.

Returns:

  • signal - Signal with Connect, Once, Wait and Fire. Callbacks receive no arguments.
  • Read-only properties such as Name never fire, since writes to them are ignored.
lua
local part = workspace:FindFirstChild("Baseplate")
if part then
    part:GetPropertyChangedSignal("Transparency"):Connect(function()
        print("Transparency is now", part.Transparency)
    end)
    part.Transparency = 0.5
end

Instance:Fire(...)

Fires a BindableEvent's Event or a ProximityPrompt's Triggered for your own connections only. The game doesn't see it.

Parameters:

  • ...: any (optional) - Values passed to each callback.
  • Any other class raises Fire: unsupported instance.
  • To activate a real prompt in the game use fireproximityprompt (see Remotes).
lua
local event = workspace:FindFirstChildOfClass("BindableEvent")
if event then
    event.Event:Connect(function(message)
        print("got", message)
    end)
    event:Fire("hello")
end

RemoteEvent:FireServer(...)

Sends a RemoteEvent to the server through the game's own networking code. The object's ClassName must be exactly RemoteEvent. See Remotes for the full rules and error list.

Parameters:

  • ...: any (optional) - Up to 16 values: booleans, numbers, strings, Vector2, Vector3, Color3, CFrame or Instances. nil and tables are rejected.

Aliases: fireserver

  • Problems raise an error that starts with FireServer: (wrong class, unsupported Roblox build, unsupported argument type, too many arguments).
  • It does not yield: the script thread waits for the game to run the call, which takes at least 0.6 seconds for a successful send.
lua
local storage = game:GetService("ReplicatedStorage")
local remote = storage and storage:FindFirstChild("SetTeam")
if remote and remote:IsA("RemoteEvent") then
    local ok, err = pcall(function()
        remote:FireServer("Red", 1)
    end)
    if not ok then
        warn(err)
    end
end

RemoteFunction:InvokeServer(...)

Invokes the RemoteFunction and returns the server's reply. See Remotes.

Parameters:

  • ...: any (optional) - Up to 16 arguments, same types as FireServer.

Returns:

  • ... - The values the server returned. Booleans, numbers, strings, Vector2, Vector3, Color3, CFrame and Instances are converted; other types come back as nil.

Aliases: invokeserver

  • Raises an error after 10 seconds without a reply, or when the server errors.

Methods documented elsewhere

MethodPage
Instance:GetAttribute(name)Attributes
Instance:SetAttribute(name, value)Attributes
Instance:GetAttributes()Attributes
Instance:GetService(name)game
Instance:IsLoaded()game
Instance:GetPlayers()game

No other methods exist. inst:Destroy(), inst:Clone() or inst:ClearAllChildren() fall through to the child lookup and fail with an attempt to call error.

Events

Every event is an engine signal with Connect, Once, Wait and Fire. Each access returns a new signal table, but connections are matched by event and object, so connecting through different accesses behaves the same.

Instance.ChildAdded

Fires with each new direct child. Watching starts the first time you read the signal.

Parameters:

  • child: Instance - Passed to the callback: the new child.

Instance.ChildRemoved

Fires with a direct child that disappeared since the previous tick. The object may already be destroyed, so its properties can read as empty.

Parameters:

  • child: Instance - Passed to the callback: the removed child.

Instance.DescendantAdded

Fires right after ChildAdded, with the same child. Despite the name it only sees direct children of the object; objects added deeper in the tree do not fire it.

Parameters:

  • descendant: Instance - Passed to the callback: the new direct child.

Player.CharacterAdded

Fires with the new character model when a player's Character pointer changes to a different model. The engine checks every player once per tick, whether or not anything is connected.

Parameters:

  • character: Instance - Passed to the callback: the new character model.
  • Characters that already exist when the engine first scans the players do not fire. There is no CharacterRemoving.
lua
local player = game:GetService("Players").LocalPlayer
player.CharacterAdded:Connect(function(character)
    print("respawned as", character:GetFullName())
end)

BindableEvent.Event

Fires only when a clover script calls :Fire(...), not when the game does.

Parameters:

  • ...: any - Passed to the callback: the values given to Fire.

RemoteEvent.OnClientEvent

Exists for compatibility but never fires from the network; only from your own :Fire(...).

Parameters:

  • ...: any - Passed to the callback: the values given to Fire.

ProximityPrompt.Triggered

Fires only when a clover script calls :Fire(...), not when a player uses the prompt.

Parameters:

  • ...: any - Passed to the callback: the values given to Fire.
  • To trigger the real prompt, or to call the game's own connections, use fireproximityprompt or firesignal (see Remotes).

Metamethods

OperationResult
tostring(inst), print(inst)The object's Name, or Instance(nil) when the stored address is not a plausible pointer.
a == btrue when both handles hold the same address. Comparing an Instance with another userdata type (such as a Vector3) raises an error; comparing it with a non-userdata value is false.
typeof(inst)"Instance"
type(inst)"userdata"
inst.keySee Indexing rules.
inst.key = valueWrites a supported property; any other key is ignored.

Attributes

Read game attributes on any Instance and attach your own.

Two layers of values

SetAttribute values live in the script engine and are cleared on VM reset. They override a game attribute with the same name. The game's own copy is only written when that attribute already exists with a compatible type (see Values you can set).

Game attributes depend on the Roblox build

Game attributes are only readable on the Roblox build the engine was verified against. On other builds you only see script values.

Watching attribute changes

There is no GetAttributeChangedSignal. SetAttribute(name, ...) fires GetPropertyChangedSignal(name) instead. Changes made by the game are not observed.

attributes.lua
local player = game:GetService("Players").LocalPlayer
if not player then
    return
end

print("Level from the game:", player:GetAttribute("Level"))

player:GetPropertyChangedSignal("Marked"):Connect(function()
    print("Marked is now", player:GetAttribute("Marked"))
end)
player:SetAttribute("Marked", true)

for name, value in pairs(player:GetAttributes()) do
    print(name, typeof(value), value)
end

Methods

Instance:GetAttribute(name)

Returns the script value stored under name if there is one, otherwise the game's attribute of that name.

Parameters:

  • name: string - Attribute name, case-sensitive. Numbers are converted to strings; other types raise an error.

Returns:

  • boolean | number | string | Vector3 | CFrame | nil - The value, or nil when neither layer has it, when the game attribute's type cannot be read, or when game attributes are unavailable on this build.
lua
local part = workspace:FindFirstChild("Door")
if part then
    local locked = part:GetAttribute("Locked")
    print("locked:", locked == true)
end

Instance:SetAttribute(name, value)

Stores a script value on this object, or removes it when value is nil. Fires GetPropertyChangedSignal(name).

Parameters:

  • name: string - Attribute name. An empty name does nothing (no value stored, no signal).
  • value: boolean | number | string | Vector3 | CFrame | nil (optional, default nil) - The value. Other types raise SetAttribute: unsupported value type.
  • Numbers and numeric strings are stored as floats: 5 reads back as 5.0.
  • Removing a script value with nil does not remove the game's attribute; the game value shows through again.
  • If the object's address is not a plausible pointer, the call does nothing.
lua
local character = workspace:FindFirstChild("Enemy")
if character then
    character:SetAttribute("Target", true)
    character:SetAttribute("Seen", os.clock())
    character:SetAttribute("Target", nil)
end

Instance:GetAttributes()

Returns every attribute the engine can see on the object as a name = value table. Game attributes are added first, then script values, so a script value replaces a game value with the same name.

Returns:

  • table - Map from attribute name to value. Empty when there are none. Game attributes whose type cannot be read are left out.

Value types

Values you can set

Lua valueStored asAlso written into an existing game attribute of type
booleanbooleanboolean
number, or a numeric stringnumber (reads back as a float)a double or float number, when the value is finite (and, for float, within the 32-bit float range). Integer-typed game attributes are not written.
stringstringnever
Vector3Vector3Vector3, when every component is finite and within ±1,000,000
CFrameCFramenever
nil or no valueremoves the script valuenever; the game attribute is left as it is
anything else (table, Color3, Vector2, Instance, function, ...)raises SetAttribute: unsupported value typenever

Writing into the game's copy overwrites the value bytes of the existing entry in place. The engine never adds attributes to the game, removes them, or changes their type.

Game attribute types the engine reads

Game attribute typeLua value
booleanboolean
32-bit or 64-bit integerinteger
float or doublenumber; non-finite values read as nil
stringstring, up to 65,536 bytes; longer strings read as nil
Vector3Vector3; components must be finite and within ±1,000,000, otherwise nil
any other typenil, and the attribute is left out of GetAttributes

Storage

  • Script values live in a table inside the engine keyed by object address, so every handle to the same object sees them. They are never saved and are cleared when the VM resets. They are not removed when the game destroys the object, so a new object that reuses the same address can show them until the next reset.
  • Game attributes are found through the object's component map. The attribute component holds its entries either as a flat array or as a hash table, with at most 4,096 entries.
  • Every read checks the container's header, collects the entries, then reads the header again. If the game changed it in between (for example by adding an attribute), the result is thrown away and the call behaves as if the object had no game attributes: GetAttribute returns the script value or nil, and GetAttributes returns only script values. String values are verified the same way before they are returned.
  • Each GetAttribute call scans the entries and compares names in game memory. Read values once and keep them in locals rather than calling it many times per frame.

Services

Lua versions of RunService, UserInputService, TweenService, ContextActionService, StarterGui and HttpService.

Getting a service

RunService, UserInputService, TweenService and HttpService are globals. Get ContextActionService and StarterGui with game:GetService.

Emulated, not the real services

These are engine tables, not Roblox's services. Setting fields like MouseBehavior changes nothing in game.

services-demo.lua
local dot = Drawing.new("Circle")
dot.Radius = 6
dot.Filled = true
dot.Color = Color3.fromRGB(255, 200, 40)
dot.Position = Vector2.new(100, 100)

local info = TweenInfo.new(0.4, Enum.EasingStyle.Back, Enum.EasingDirection.Out)

RunService:BindToRenderStep("follow", Enum.RenderPriority.Last.Value, function(dt)
    if UserInputService:IsKeyDown(Enum.KeyCode.LeftShift) then
        dot.Position = UserInputService:GetMouseLocation()
    end
end)

UserInputService.InputBegan:Connect(function(input)
    if input.UserInputType == Enum.UserInputType.MouseButton1 then
        TweenService:Create(dot, info, {Radius = 18}):Play()
    elseif input.UserInputType == Enum.UserInputType.MouseButton2 then
        TweenService:Create(dot, info, {Radius = 6}):Play()
    end
end)

RunService

Heartbeat, RenderStepped and Stepped are one engine event (see Signals). It fires once per scheduler tick with the tick length dt in seconds and is not synchronized with Roblox frames. Handlers cannot yield.

RunService.Heartbeat

Fires once per scheduler tick, after sleeping threads, delays and tweens have been processed.

Parameters:

  • dt: number - Seconds since the previous tick.

Aliases: RunService.PostSimulation

lua
local frames = 0
RunService.Heartbeat:Connect(function(dt)
    frames = frames + 1
end)

RunService.RenderStepped

The same event as Heartbeat, under its render name.

Parameters:

  • dt: number - Seconds since the previous tick.

Aliases: RunService.PreRender

RunService.Stepped

The same event as Heartbeat. Unlike Roblox, which passes (time, dt), handlers receive only dt.

Parameters:

  • dt: number - Seconds since the previous tick.

Aliases: RunService.PreAnimation, RunService.PreSimulation

RunService:BindToRenderStep(name, priority, callback)

Connects callback to RenderStepped under name. Binding a name that is already bound disconnects the previous callback first.

Parameters:

  • name: string - Key used to unbind or replace the binding.
  • priority: number - Accepted for compatibility and ignored; bindings run in no particular order.
  • callback: function - Called with dt every tick.
  • The callback runs as a signal handler: it cannot yield, and an error disconnects it.
  • If the callback errors, unbinding that name later can remove an unrelated connection.
lua
local cursor = Drawing.new("Circle")
cursor.Radius = 4

RunService:BindToRenderStep("cursor", Enum.RenderPriority.Last.Value, function()
    cursor.Position = UserInputService:GetMouseLocation()
end)

task.wait(10)
RunService:UnbindFromRenderStep("cursor")
cursor:Remove()

RunService:UnbindFromRenderStep(name)

Disconnects the callback bound under name. Unknown names are ignored.

Parameters:

  • name: string - Name given to BindToRenderStep.

The environment checks always return the same values:

MethodReturns
RunService:IsClient()true
RunService:IsServer()false
RunService:IsStudio()false
RunService:IsRunning()true
RunService:IsRunMode()false
RunService:IsEdit()false

UserInputService

Keyboard and mouse state comes from Windows key polling, not from Roblox. Key and button queries return false or an empty table while Roblox is not the foreground window. Only keys with a mapping in the KeyCode table are reported.

Events

UserInputService.InputBegan

Fires when a key or mouse button goes down while Roblox is focused.

Parameters:

  • input: InputObject - See InputObject; UserInputState is Begin.
  • gameProcessed: boolean - Always false.
lua
UserInputService.InputBegan:Connect(function(input, gameProcessed)
    if input.KeyCode == Enum.KeyCode.Insert then
        print("toggle menu")
    end
end)

UserInputService.InputEnded

Fires when a key or button whose press was delivered by InputBegan is released, even if Roblox lost focus in between.

Parameters:

  • input: InputObject - UserInputState is End.
  • gameProcessed: boolean - Always false.

These Lua-side signals exist so scripts can connect to them, but the engine never fires them (see Lua-side signals):

SignalFired by the engine
UserInputService.InputChangedNever
UserInputService.TextBoxFocusedNever
UserInputService.TextBoxFocusReleasedNever
UserInputService.WindowFocusedNever
UserInputService.WindowFocusReleasedNever

Fields

FieldValue
UserInputService.MouseEnabledtrue
UserInputService.KeyboardEnabledtrue
UserInputService.TouchEnabledfalse
UserInputService.GamepadEnabledfalse
UserInputService.VREnabledfalse
UserInputService.MouseIconEnabledtrue
UserInputService.MouseBehaviorEnum.MouseBehavior.Default

Methods

UserInputService:IsKeyDown(keyCode)

Returns whether the key is held right now.

Parameters:

  • keyCode: EnumItem - An Enum.KeyCode item.

Returns:

  • boolean - false when Roblox is not the foreground window or the KeyCode has no key mapping.
lua
if UserInputService:IsKeyDown(Enum.KeyCode.LeftControl) then
    print("ctrl is held")
end

UserInputService:IsMouseButtonPressed(inputType)

Returns whether a mouse button is held right now.

Parameters:

  • inputType: EnumItem - Enum.UserInputType.MouseButton1, MouseButton2 or MouseButton3.

Returns:

  • boolean - false for any other input type or when Roblox is not the foreground window.

UserInputService:GetMouseLocation()

Returns the cursor position in pixels, relative to the top-left corner of the foreground window's client area (normally Roblox).

Returns:

  • Vector2

UserInputService:GetMouseDelta()

Returns how far the cursor moved, in pixels, since the previous call to GetMouseDelta from any script. The first call returns (0, 0).

Returns:

  • Vector2

UserInputService:GetKeysPressed()

Returns one InputObject for every mapped keyboard key that is held, in no particular order, each with UserInputState Begin. Mouse buttons are not included.

Returns:

  • {InputObject} - Empty when Roblox is not the foreground window.

UserInputService:GetMouseButtonsPressed()

Returns InputObjects for the held mouse buttons, checked in the order MouseButton1, MouseButton2, MouseButton3.

Returns:

  • {InputObject} - Empty when Roblox is not the foreground window.

UserInputService:GetFocusedTextBox()

Always returns nil.

Returns:

  • nil

UserInputService:GetLastInputType()

Always returns Enum.UserInputType.Keyboard.

Returns:

  • EnumItem

UserInputService:IsGamepadButtonDown()

Always returns false; arguments are ignored.

Returns:

  • boolean

InputObject

Input events, GetKeysPressed and GetMouseButtonsPressed produce plain Lua tables with the fields below. A new table is built for every event.

FieldTypeValue
KeyCodeEnumItemThe key's Enum.KeyCode item; Enum.KeyCode.Unknown for mouse buttons
UserInputTypeEnumItemMouseButton1 (left), MouseButton2 (right), MouseButton3 (middle) or Keyboard
UserInputStateEnumItemBegin for a press, End for a release
PositionVector3Cursor position in pixels as (x, y, 0), read when the object is built
DeltaVector3Always (0, 0, 0)

TweenService

Animates number, Vector2, Vector3 and Color3 values on a Lua table or a Drawing object. Tweens advance once per scheduler tick and write each new value straight into the target.

Local targets only

TweenService:Create only tweens Lua tables and Drawing objects, not game Instances. Every goal property must already exist with the same type.

TweenService:Create(target, tweenInfo, goals)

Creates a stopped tween that moves each property in goals from its value at Play time to the goal value.

Parameters:

  • target: table | Drawing - Object whose properties are animated.
  • tweenInfo: TweenInfo | table | number (optional) - Timing. A number is used as Time, nil uses every default, and any table with TweenInfo fields works.
  • goals: table - Property name to end value. Values must be finite numbers, Vector2, Vector3 or Color3.

Returns:

  • tween: Tween - See Tween. Call Play to start it.
  • Can also be called with a dot: TweenService.Create(target, tweenInfo, goals).
  • Raises an error for an unsupported target, empty goals, or a goal whose type differs from the current value.
lua
local fade = {Alpha = 0}
local tween = TweenService:Create(fade, TweenInfo.new(2, Enum.EasingStyle.Sine), {Alpha = 1})
tween:Play()
tween.Completed:Wait()
print("faded in", fade.Alpha)

TweenService:GetValue(alpha, [easingStyle], [easingDirection])

Returns the eased value for alpha without creating a tween.

Parameters:

  • alpha: number - Progress from 0 to 1; values outside are clamped. Must be finite.
  • easingStyle: EnumItem | string (optional, default "Quad") - An Enum.EasingStyle item or its name.
  • easingDirection: EnumItem | string (optional, default "Out") - An Enum.EasingDirection item or its name.

Returns:

  • number
  • Can also be called with a dot.
  • Raises an error for a non-finite alpha or an unknown style or direction.
lua
for i = 0, 4 do
    print(i / 4, TweenService:GetValue(i / 4, Enum.EasingStyle.Quad, Enum.EasingDirection.InOut))
end

TweenInfo

TweenInfo.new([time], [easingStyle], [easingDirection], [repeatCount], [reverses], [delayTime])

Validates the timing values and returns them as a plain table. Call it with a dot.

Parameters:

  • time: number (optional, default 1) - Seconds for one pass; finite and at least 0.
  • easingStyle: EnumItem | string (optional, default "Quad") - An Enum.EasingStyle item or its name.
  • easingDirection: EnumItem | string (optional, default "Out") - An Enum.EasingDirection item or its name.
  • repeatCount: number (optional, default 0) - Extra cycles after the first. A whole number from -1 to 1000000; -1 repeats forever and needs a non-zero time or delayTime.
  • reverses: boolean (optional, default false) - Play each cycle forward, then backward to the start value.
  • delayTime: number (optional, default 0) - Seconds to wait at the start of every cycle; finite and at least 0.

Returns:

  • info: table - Fields Time, EasingStyle, EasingDirection, RepeatCount, Reverses and DelayTime. Style and direction are stored as name strings such as "Quad", not as EnumItems.
  • Invalid values raise an error immediately.
lua
local info = TweenInfo.new(0.5, Enum.EasingStyle.Back, Enum.EasingDirection.Out, 0, true, 0.1)
print(info.Time, info.EasingStyle, info.Reverses)

One cycle is DelayTime + Time (doubled Time with Reverses), repeated RepeatCount + 1 times. The final value is written before Completed fires.

Styles: Linear, Sine, Back, Quad, Quart, Quint, Bounce, Elastic, Exponential, Circular, Cubic (see EasingStyle). Directions: In, Out, InOut.

Tween

Tween properties are read-only: assigning any key raises Tween properties are read-only. After Destroy, Play, Pause and Cancel raise Tween has been destroyed.

Tween:Play()

Starts or resumes the tween. Cancels other tweens on the same target that animate the same properties.

lua
local box = {Size = Vector2.new(10, 10)}
local grow = TweenService:Create(box, 0.25, {Size = Vector2.new(40, 40)})
grow:Play()

Tween:Pause()

Stops a playing tween and keeps its progress; PlaybackState becomes Paused. Completed does not fire.

Tween:Cancel()

Stops the tween and fires Completed with Cancelled. Values already written stay.

Tween:Destroy()

Stops the tween without firing Completed, disconnects every Completed handler and releases the target. Calling it again does nothing.

Tween.PlaybackState

Current state: Begin after creation, then Delayed, Playing, Paused, Completed or Cancelled. These are bare Enum.PlaybackState items, so compare them with ==.

Read-only.

Returns:

  • EnumItem

Tween.Completed

Fires when the tween finishes or is cancelled. Handlers may yield.

Read-only.

Parameters:

  • playbackState: EnumItem - Enum.PlaybackState.Completed or Enum.PlaybackState.Cancelled.
lua
local state = {Value = 0}
local tween = TweenService:Create(state, 1, {Value = 100})
tween.Completed:Connect(function(playbackState)
    print("tween ended:", playbackState.Name, state.Value)
end)
tween:Play()

Tween.Instance

The target table or Drawing object; nil after Destroy.

Read-only.

Returns:

  • table | Drawing | nil

Tween.TweenInfo

A fresh copy of the tween's TweenInfo table each time it is read.

Read-only.

Returns:

  • table

Tween.Error

The error that stopped the tween during an update, otherwise nil.

Read-only.

Returns:

  • string | nil

ContextActionService

Get it with game:GetService("ContextActionService"). Bindings are built on UserInputService.InputBegan and InputEnded, so the same key mapping and focus rules apply, and the callback runs on both press and release.

ContextActionService:BindAction(name, callback, createTouchButton, ...)

Binds callback to the given inputs under name, replacing any binding with the same name.

Parameters:

  • name: string - Action name, passed back to the callback.
  • callback: function - Called as callback(name, state, input) with an InputObject.
  • createTouchButton: boolean - Ignored.
  • ...: EnumItem - Enum.KeyCode or Enum.UserInputType items. An input matches when its KeyCode or its UserInputType equals one of them.
  • The callback runs inside a signal handler, so it cannot yield.
  • If the callback errors, its connection is dropped and a later UnbindAction can remove an unrelated one.
lua
local CAS = game:GetService("ContextActionService")
CAS:BindAction("sprint", function(name, state, input)
    if state == Enum.UserInputState.Begin then
        print(name, "started with", input.KeyCode)
    else
        print(name, "stopped")
    end
end, false, Enum.KeyCode.LeftShift, Enum.KeyCode.RightShift)

ContextActionService:BindActionAtPriority(name, callback, createTouchButton, priority, ...)

Same as BindAction; priority is ignored.

ContextActionService:UnbindAction(name)

Removes the binding with that name. Unknown names are ignored.

Parameters:

  • name: string - Name given to BindAction.

ContextActionService:UnbindAllActions()

Removes every binding.

ContextActionService:GetBoundActionInfo()

Always returns an empty table.

Returns:

  • table

StarterGui

Get it with game:GetService("StarterGui"). Only notifications do anything.

StarterGui:SetCore(name, value)

Only "SendNotification" does anything: it shows a notify toast with Title: Text.

Parameters:

  • name: string - Only "SendNotification" is handled.
  • value: table - Title and Text, converted with tostring. Duration does not control how long the notification stays, and other keys are ignored.
lua
local StarterGui = game:GetService("StarterGui")
StarterGui:SetCore("SendNotification", {Title = "clover", Text = "Script loaded"})

StarterGui:GetCore()

Always returns nil.

Returns:

  • nil

StarterGui:SetCoreGuiEnabled()

Does nothing.

StarterGui:GetCoreGuiEnabled()

Always returns true.

Returns:

  • boolean

HttpService

The global HttpService wraps the engine's HTTP and JSON functions. Network calls need the unsafe API toggle; see http for request options, limits and the response format.

MemberDescription
HttpService:GetAsync(url)GET request. Returns the body, raises an error on failure. Needs the unsafe API
HttpService:PostAsync(url, data, [contentType])POST with Content-Type defaulting to application/json. Returns the body, raises HTTP <status> when the request does not succeed. Needs the unsafe API
HttpService:RequestAsync(options)Same as request(options): returns a response table. Needs the unsafe API
HttpService:JSONEncode(value)Encodes a Lua value as a JSON string
HttpService:JSONDecode(json)Decodes a JSON string; raises JSONDecode: invalid JSON on bad input
HttpService:GenerateGUID([wrapInBraces])Random version 4 style GUID in lowercase hex, wrapped in braces unless the argument is false
HttpService:UrlEncode(text)Percent-encodes every character except letters, digits, -, _, . and ~
HttpService.HttpEnabledAlways true

players

Plain-table snapshots of the players in the server.

Snapshots, not Instances

Each call returns fresh tables from the engine's player scanner (refreshed about every 80 ms). They don't update; call again. For Instances use game:GetService("Players").

Cheap enough for every frame

Both functions are safe to call from a render callback. Each call costs one memory read per player for pos, so call clover.players() once per frame and reuse the result.

list-players.lua
local me = clover.local_player()
if not me then
    print("no local player yet")
    return
end

for _, p in ipairs(clover.players()) do
    if not p.is_local and p.humanoid ~= 0 then
        local dx = p.pos.x - me.pos.x
        local dy = p.pos.y - me.pos.y
        local dz = p.pos.z - me.pos.z
        local distance = math.sqrt(dx * dx + dy * dy + dz * dz)
        print(string.format("%s (%d) %.0f studs", p.name, p.user_id, distance))
    end
end

Functions

clover.players()

Returns every player the scanner currently knows about, the local player included.

Returns:

  • players: table - Array of player tables, indexed from 1. Never nil: it is an empty table outside a game, while the game is loading, or before the scanner has found the Players service.
  • The order of the array is not stable between calls. Sort it yourself if you need a fixed order.
  • Entries with an empty name and a user_id of 0 are left out.
  • Players without a character are normally still listed, with humanoid set to 0 and pos at 0, 0, 0. Check for that before projecting pos.

clover.local_player()

Returns the entry for your own player from the same snapshot that clover.players() uses.

Returns:

  • player: table | nil - A player table without the is_local field, or nil when the snapshot has no local player (outside a game, while loading, or before the first scan).
lua
local me = clover.local_player()
if me then
    print(me.name, me.user_id, me.pos.x, me.pos.y, me.pos.z)
end

Player table

FieldTypeDescription
namestringThe player's display name, or the account name when the display name is empty.
user_idintegerThe player's UserId.
teamintegerA team identifier. Normally the memory address of the player's Team instance, and 0 when they have no team. In a few games with built-in support the scanner substitutes its own small ids (for example 1 and 2), and free-for-all modes can report 0 for everyone. Only compare it with other team values; it is not a name.
is_localbooleantrue for your own player. Present only in clover.players() entries.
humanoidintegerMemory address of the character's Humanoid, or 0 when the player has no character or no Humanoid was found.
addressintegerMemory address of the Player instance, usable with the memory functions. It can be 0 in games where the scanner builds players from character models instead of Player instances.
postableWorld position of the character's HumanoidRootPart as { x, y, z } (lowercase keys, plain numbers, not a Vector3). Read when the function is called. All three are 0 when no HumanoidRootPart is known, for example while the player has no character.

To use pos with the data types, convert it: Vector3.new(p.pos.x, p.pos.y, p.pos.z). To put it on screen, pass the three numbers to world_to_screen.

enemies.lua
local function enemies()
    local me = clover.local_player()
    local out = {}
    for _, p in ipairs(clover.players()) do
        local teammate = me ~= nil and p.team ~= 0 and p.team == me.team
        if not p.is_local and not teammate and p.humanoid ~= 0 then
            out[#out + 1] = p
        end
    end
    return out
end

print(#enemies(), "enemies")

draw

Immediate-mode 2D drawing that you repeat every frame, plus world-to-screen projection.

Redraw every frame

Each pass's draw commands replace the previous ones, so draw from a callback that runs every pass: clover.on_render(fn) or cheat.register("onDraw", fn) (see cheat).

Pixels, top-left origin

All values are screen pixels with (0, 0) at the top-left. Use world_to_screen for world positions.

Colors are 0-1, and Color3 is not accepted

Colors are a { R, G, B, A } table or four numbers, each 0 to 1. Color3 raises an error. See Colors.

Limits and layering

Up to 500000 commands per pass. They render in call order, behind Drawing objects and the clover menu.

crosshair.lua
local white = { R = 1, G = 1, B = 1, A = 1 }
local shade = { R = 0, G = 0, B = 0, A = 0.5 }

clover.on_render(function()
    local w, h = clover.screen_size()
    if w == 0 then
        return
    end
    local cx, cy = w / 2, h / 2
    draw.Line(cx - 8, cy, cx + 8, cy, white, 1)
    draw.Line(cx, cy - 8, cx, cy + 8, white, 1)

    local label = "clover"
    local tw, th = draw.GetTextSize(label)
    draw.RectangleFilled(10, 10, 10 + tw + 12, 10 + th + 8, shade, 4)
    draw.Text(16, 14, label, white)
end)

Each function also has snake_case and camelCase aliases plus the raw clover.draw_* name. Only draw.GetTextSize returns a value.

Colors

Every [color] parameter on this page is read the same way:

FormSlots usedDetails
Table1Fields R, G, B, A (or lowercase r, g, b, a). A missing or non-number field counts as 1. Array-style tables such as {1, 0, 0} are not read, so they come out white.
Numbers4Four separate numbers r, g, b, a. Each missing or nil number counts as 1.
Omitted0Opaque white.
  • All components are clamped to 0-1. Use n / 255 to convert byte values.
  • The number form takes four argument slots, so the parameters after it shift by four. Always pass all four numbers: draw.Line(0, 0, 50, 50, 1, 0, 0, 2) reads 2 as alpha (clamped to 1) and leaves the thickness at its default.
  • You cannot skip the color and still pass a later argument. A nil in the color slot is read as the red number, and the next arguments are consumed as green, blue and alpha.
  • Color3 values are userdata and are rejected with an argument error. Convert them with { R = c.R, G = c.G, B = c.B, A = 1 }. The value of a Library colorpicker is already a plain { R, G, B } table and can be passed directly.
colors.lua
local red = { R = 1, G = 0, B = 0, A = 1 }
local accent = Color3.fromRGB(255, 170, 0)

local function rgba(c, a)
    return { R = c.R, G = c.G, B = c.B, A = a or 1 }
end

clover.on_render(function()
    draw.Line(10, 10, 200, 10, red, 2)
    draw.Line(10, 20, 200, 20, 0, 1, 0, 1, 2)
    draw.Line(10, 30, 200, 30, rgba(accent, 0.5), 2)
end)

Shapes

draw.Line(x1, y1, x2, y2, [color], [thickness])

Draws a straight line from (x1, y1) to (x2, y2).

Parameters:

  • x1: number - Start x in pixels.
  • y1: number - Start y in pixels.
  • x2: number - End x in pixels.
  • y2: number - End y in pixels.
  • color: table | numbers (optional, default white) - See Colors.
  • thickness: number (optional, default 1) - Line width in pixels. Zero or negative values draw at 1.

Aliases: draw.line, clover.draw_line

draw.Rectangle(x1, y1, x2, y2, [color], [thickness], [rounding])

Draws the outline of an axis-aligned rectangle. (x1, y1) and (x2, y2) are two opposite corners, not a position and a size: pass x + w, y + h as the second pair.

Parameters:

  • x1: number - Left edge in pixels.
  • y1: number - Top edge in pixels.
  • x2: number - Right edge in pixels.
  • y2: number - Bottom edge in pixels.
  • color: table | numbers (optional, default white) - See Colors.
  • thickness: number (optional, default 1) - Outline width in pixels. Zero or negative values draw at 1.
  • rounding: number (optional, default 0) - Corner radius in pixels, applied to all four corners.

Aliases: draw.rectangle, clover.draw_rect

lua
local box = { R = 0.3, G = 0.8, B = 1, A = 1 }
local x, y, w, h = 100, 100, 80, 40

clover.on_render(function()
    draw.Rectangle(x, y, x + w, y + h, box, 2, 6)
end)

clover.draw_box(x1, y1, x2, y2, [color], [thickness])

Draws a rectangle outline. Same as draw.Rectangle without rounding.

Parameters:

  • x1: number - Left edge in pixels.
  • y1: number - Top edge in pixels.
  • x2: number - Right edge in pixels.
  • y2: number - Bottom edge in pixels.
  • color: table | numbers (optional, default white) - See Colors.
  • thickness: number (optional, default 1) - Outline width in pixels. Zero or negative values draw at 1.

draw.RectangleFilled(x1, y1, x2, y2, [color], [rounding])

Fills an axis-aligned rectangle between two opposite corners.

Parameters:

  • x1: number - Left edge in pixels.
  • y1: number - Top edge in pixels.
  • x2: number - Right edge in pixels.
  • y2: number - Bottom edge in pixels.
  • color: table | numbers (optional, default white) - See Colors. Use alpha below 1 for a translucent panel.
  • rounding: number (optional, default 0) - Corner radius in pixels.

Aliases: draw.rectangle_filled, draw.rectangleFilled, clover.draw_rect_filled

draw.Circle(x, y, radius, [color], [thickness])

Draws the outline of a circle centered on (x, y). The number of segments is chosen automatically from the radius.

Parameters:

  • x: number - Center x in pixels.
  • y: number - Center y in pixels.
  • radius: number - Radius in pixels.
  • color: table | numbers (optional, default white) - See Colors.
  • thickness: number (optional, default 1) - Outline width in pixels. Zero or negative values draw at 1.

Aliases: draw.circle, clover.draw_circle

draw.CircleFilled(x, y, radius, [color], [segments])

Fills a circle centered on (x, y).

Parameters:

  • x: number - Center x in pixels.
  • y: number - Center y in pixels.
  • radius: number - Radius in pixels.
  • color: table | numbers (optional, default white) - See Colors.
  • segments: integer (optional, default 0) - Number of segments. 0 or less picks a count automatically from the radius; a small value such as 6 draws a polygon.

Aliases: draw.circle_filled, draw.circleFilled, clover.draw_circle_filled

draw.Triangle(x1, y1, x2, y2, x3, y3, [color], [thickness])

Draws the outline of the triangle with corners (x1, y1), (x2, y2) and (x3, y3).

Parameters:

  • x1: number - First corner x.
  • y1: number - First corner y.
  • x2: number - Second corner x.
  • y2: number - Second corner y.
  • x3: number - Third corner x.
  • y3: number - Third corner y.
  • color: table | numbers (optional, default white) - See Colors.
  • thickness: number (optional, default 1) - Outline width in pixels. Zero or negative values draw at 1.

Aliases: draw.triangle, clover.draw_triangle

draw.TriangleFilled(x1, y1, x2, y2, x3, y3, [color])

Fills the triangle with corners (x1, y1), (x2, y2) and (x3, y3).

Parameters:

  • x1: number - First corner x.
  • y1: number - First corner y.
  • x2: number - Second corner x.
  • y2: number - Second corner y.
  • x3: number - Third corner x.
  • y3: number - Third corner y.
  • color: table | numbers (optional, default white) - See Colors.

Aliases: draw.triangle_filled, draw.triangleFilled, clover.draw_triangle_filled

draw.Gradient(x1, y1, x2, y2, [color1], [color2], [vertical])

Fills the rectangle between two opposite corners with a two-color linear gradient. By default color1 is on the left edge and color2 on the right edge. There is no rounding.

Parameters:

  • x1: number - Left edge in pixels.
  • y1: number - Top edge in pixels.
  • x2: number - Right edge in pixels.
  • y2: number - Bottom edge in pixels.
  • color1: table | numbers (optional, default white) - Start color. See Colors.
  • color2: table | numbers (optional, default white) - End color.
  • vertical: boolean (optional, default false) - true for top-to-bottom. Defaults to left-to-right.

Aliases: draw.gradient, clover.draw_gradient

lua
local red = { R = 1, G = 0, B = 0, A = 1 }
local blue = { R = 0, G = 0, B = 1, A = 1 }
local clear = { R = 0, G = 0, B = 0, A = 0 }
local dark = { R = 0, G = 0, B = 0, A = 0.8 }

clover.on_render(function()
    draw.Gradient(20, 60, 220, 80, red, blue)
    draw.Gradient(20, 90, 220, 140, clear, dark, true)
end)

draw.Polyline(points, [color], [thickness], [closed])

Draws connected line segments through a list of points.

Parameters:

  • points: table - Array of { X, Y } tables or flat numbers read as x, y pairs. Vector2 values are skipped. Needs at least two points.
  • color: table | numbers (optional, default white) - See Colors.
  • thickness: number (optional, default 1) - Line width in pixels. Zero or negative values draw at 1.
  • closed: boolean (optional, default false) - Any truthy value also connects the last point back to the first. You must pass thickness to reach this argument.

Aliases: draw.polyline, clover.draw_polyline

lua
local cyan = { R = 0.4, G = 0.8, B = 1, A = 1 }
local roof = { { x = 100, y = 100 }, { x = 160, y = 60 }, { x = 220, y = 100 } }
local zigzag = { 100, 140, 130, 120, 160, 140, 190, 120 }

clover.on_render(function()
    draw.Polyline(roof, cyan, 2, true)
    draw.Polyline(zigzag, cyan, 1)
end)

Text

Text uses the overlay's ImGui font at its default size. There is no font or size argument; for sized text use a Text object from Drawing.

draw.Text(x, y, text, [color])

Draws a string with its top-left corner at (x, y). Newlines start a new line.

Parameters:

  • x: number - Left edge in pixels.
  • y: number - Top edge in pixels.
  • text: string - The text. Numbers are converted to strings; other types raise an error.
  • color: table | numbers (optional, default white) - See Colors.

Aliases: draw.text, clover.draw_text

draw.TextOutlined(x, y, text, [color], [outlineColor])

Draws text with a 1-pixel outline, readable on any background. Good for ESP labels.

Parameters:

  • x: number - Left edge in pixels.
  • y: number - Top edge in pixels.
  • text: string - The text. Numbers are converted to strings; other types raise an error.
  • color: table | numbers (optional, default white) - Fill color. See Colors.
  • outlineColor: table | numbers (optional, default opaque black) - Outline color. Read only when an argument is present after color; an explicit nil there counts as present and gives a white outline.

Aliases: draw.text_outlined, draw.textOutlined, clover.draw_text_outlined

draw.GetTextSize(text)

Measures a string with the overlay's current ImGui font, the font that draw.Text and draw.TextOutlined draw with. Use it to center or right-align labels.

Parameters:

  • text: string - The text to measure. Numbers are converted to strings.

Returns:

  • width: number - Width in pixels.
  • height: number - Height in pixels. Both values are 0 while the overlay has no ImGui context or font yet.

Aliases: draw.get_text_size, draw.getTextSize, clover.draw_get_text_size

lua
local white = { R = 1, G = 1, B = 1, A = 1 }

clover.on_render(function()
    local w = clover.screen_size()
    local text = "centered"
    local tw = draw.GetTextSize(text)
    draw.TextOutlined(w / 2 - tw / 2, 40, text, white)
end)

Screen and projection

world_to_screen(x, y, z)

Projects a world position onto the screen with the game camera's current view matrix. The result is in the same pixel space as the draw functions.

Parameters:

  • x: number - World X.
  • y: number - World Y.
  • z: number - World Z. A Vector3 is not accepted; pass v.X, v.Y, v.Z.

Returns:

  • x: number - Screen x in pixels.
  • y: number - Screen y in pixels.
  • onScreen: boolean - true when the point is inside the view on both axes.

Aliases: WorldToScreen, clover.world_to_screen, clover.WorldToScreen

  • Returns 0, 0, false when the camera cannot be read (for example outside a game) or when the point is behind the camera or extremely close to it.
  • Points off-screen still return their coordinates, with false.
lua
local yellow = { R = 1, G = 0.9, B = 0.2, A = 1 }

clover.on_render(function()
    local me = clover.local_player()
    if not me then
        return
    end
    local x, y, onScreen = world_to_screen(me.pos.x, me.pos.y, me.pos.z)
    if onScreen then
        draw.CircleFilled(x, y, 4, yellow)
    end
end)

clover.screen_size()

Returns the size of the game view in pixels, read from the engine. This is the space that world_to_screen maps into. cheat.GetWindowSize returns the same values.

Returns:

  • width: number - Width in pixels, or 0 when the size cannot be read.
  • height: number - Height in pixels, or 0 when the size cannot be read.

Drawing

Retained objects: create a Line, Text, Circle or Square once and it stays on screen until removed.

Transparency 0 is opaque

Transparency 0 is opaque and 1 is invisible. Some executors use the opposite.

Objects live until removed

Objects stay until :Remove(), Drawing.clear or a VM reset. Losing your reference does not remove them.

Pixels and layering

Screen pixels, (0, 0) top-left. Higher ZIndex draws on top. Always above draw output, below the clover menu.

drawing-hello.lua
local label = Drawing.new("Text")
label.Text = "clover"
label.Size = 18
label.Center = true
label.Outline = true
label.Color = Color3.fromRGB(255, 200, 80)
label.Position = Vector2.new(400, 300)

local ring = Drawing.new("Circle")
ring.Position = Vector2.new(400, 300)
ring.Radius = 40
ring.Thickness = 2
ring.Color = Color3.new(1, 1, 1)

task.delay(5, function()
    label:Remove()
    ring:Remove()
end)

Functions

Drawing.new(type)

Creates a drawing object. It is visible right away, so set its properties immediately.

Parameters:

  • type: string - "Line", "Text", "Circle" or "Square" (case-insensitive). Anything else raises an error.

Returns:

  • object: Drawing - The new object. typeof(object) is "Drawing" and tostring(object) is "Drawing(Line)", "Drawing(Text)", "Drawing(Circle)" or "Drawing(Square)".

Drawing.clear()

Removes every drawing object created by any script. Existing handles behave like removed objects afterwards.

Aliases: cleardrawcache

Methods

Drawing:Remove()

Removes the object for good. Afterwards properties read as nil. Safe to call twice.

Aliases: Drawing:Destroy

Common properties

These exist on every type. Reading a property that does not belong to the object's type returns nil, and writing one is silently ignored (for example Radius on a Line).

Drawing.Visible

boolean, default true. Assignments use Lua truthiness, so nil hides the object. A hidden object keeps its properties and can be shown again.

Drawing.ZIndex

integer, default 0. Lower values draw first, higher values on top; equal values keep creation order. Assigning a number without an integer value (such as 1.5) or a non-number raises an error.

Drawing.Transparency

number, default 0. 0 is opaque, 1 is invisible.

Drawing.Color

Color3, default white. Also accepts a { R, G, B } table with 0-1 values. Alpha comes from Transparency.

Drawing.Thickness

number, default 1. Stroke width in pixels for lines and unfilled shapes.

Positions and sizes typed Vector2 below accept a Vector2 or a table with X/x and Y/y fields (a missing field counts as 0); any other value is silently ignored. Reading them returns a Vector2. Number properties raise an error when assigned a non-number.

Line

PropertyTypeDefaultDescription
FromVector2(0, 0)Start point in pixels.
ToVector2(0, 0)End point in pixels.

Text

PropertyTypeDefaultDescription
Textstring""The string drawn. Numbers are converted; other types raise an error.
PositionVector2(0, 0)Top-left corner of the text, or its center when Center is true.
Sizenumber13Font size in pixels. 0 or less draws at 13.
CenterbooleanfalseCenters the text both horizontally and vertically on Position.
OutlinebooleanfalseAdds a 1-pixel black outline (four shifted copies) with the same opacity as the text.
TextBoundsVector2read-onlyRough size estimate: height is Size (13 when Size is 0 or less) and width is the byte length of Text times that height times 0.5. It is not measured from the font; writes are ignored.

Text uses the overlay's ImGui font; there is no Font property. For an exact measurement of the default-size font use draw.GetTextSize.

Circle

PropertyTypeDefaultDescription
PositionVector2(0, 0)Center in pixels.
Radiusnumber50Radius in pixels.
FilledbooleanfalseFills the circle instead of drawing its outline.
NumSidesinteger64Number of segments; values below 3 draw with 3. Assigning a number without an integer value raises an error.

Square

PropertyTypeDefaultDescription
PositionVector2(0, 0)Top-left corner in pixels.
SizeVector2(100, 100)Width and height in pixels.
FilledbooleanfalseFills the rectangle instead of drawing its outline.

Tweening

Drawing objects are valid targets for TweenService:Create (see Services). Number, Vector2 and Color3 properties can be tweened; ZIndex, NumSides and TextBounds cannot and raise an error.

fade-out.lua
local label = Drawing.new("Text")
label.Text = "saved"
label.Size = 16
label.Outline = true
label.Position = Vector2.new(20, 20)

local fade = TweenService:Create(label, TweenInfo.new(1.5), { Transparency = 1 })
fade.Completed:Connect(function()
    label:Remove()
end)
fade:Play()

utility

Random numbers, timing, cursor position, menu state and clipboard.

The clipboard needs the unsafe API

GetClipboard and SetClipboard need Allow unsafe Lua API. Everything else works without it.

One function, several names

Every helper also exists in snake_case, camelCase and as clover.utility_*; see Aliases.

cursor-rate.lua
local count = 0
local rate = 0
local started = utility.GetTickCount()

cheat.register("onDraw", function()
    count = count + 1
    local now = utility.GetTickCount()
    if now - started >= 1000 then
        rate = count
        count = 0
        started = now
    end
    if utility.GetMenuState() then
        return
    end
    local pos = utility.GetMousePos()
    draw.Text(pos.X + 14, pos.Y + 14, rate .. " callbacks/s", 1, 1, 1, 1)
end)

Functions

utility.RandomInt(min, max)

Returns a random integer from min to max, inclusive. Independent of math.random.

Parameters:

  • min: integer - Lower bound. Must have an integer value: a number with a fractional part or a non-numeric value raises an error.
  • max: integer - Upper bound, same rules as min.

Returns:

  • integer - The random value. When max is less than min, min is returned unchanged.

Aliases: utility.random_int, utility.randomInt, clover.utility_random_int

lua
local roll = utility.RandomInt(1, 6)
print("rolled", roll)

utility.RandomFloat(min, max)

Returns a uniformly distributed random float from min up to, but not including, max. Uses its own generator of the same kind as RandomInt.

Parameters:

  • min: number - Lower bound.
  • max: number - Upper bound.

Returns:

  • number - The random value.

Aliases: utility.random_float, utility.randomFloat, clover.utility_random_float

  • Keep min less than or equal to max. Unlike RandomInt there is no special case for reversed bounds.
lua
local jitter = utility.RandomFloat(-0.5, 0.5)
print(string.format("jitter %.3f", jitter))

utility.GetTickCount()

Returns the number of milliseconds since Windows started (GetTickCount64). Subtract two readings to measure an interval.

Returns:

  • integer - Milliseconds since system start.

Aliases: utility.get_tick_count, utility.getTickCount, clover.utility_get_tick_count

  • For seconds since the Unix epoch use the global tick(), listed on Globals.
lua
local started = utility.GetTickCount()
task.delay(2, function()
    print("elapsed ms", utility.GetTickCount() - started)
end)

utility.GetDeltaTime()

Returns the time in seconds between the overlay's last two rendered frames (ImGui's DeltaTime). Returns 0 while the overlay's ImGui context does not exist.

Returns:

  • number - Overlay frame time in seconds, or 0.

Aliases: utility.get_delta_time, utility.getDeltaTime, clover.utility_get_delta_time

  • This is the overlay's frame time. RunService.Heartbeat handlers get the script loop's time instead.

utility.GetMousePos()

Returns the cursor position in pixels, matching draw coordinates while Roblox is focused.

Returns:

  • table - { X = x, Y = y, x = x, y = y }. Both spellings hold the same float values.

Aliases: utility.get_mouse_pos, utility.getMousePos, clover.utility_get_mouse_pos, mouse.GetPosition, mouse.get_position, mouse.getPosition

  • When no window has focus the values are screen coordinates.
  • The global getmousepos returns the same position as two numbers instead of a table.
lua
cheat.register("onDraw", function()
    local pos = utility.GetMousePos()
    draw.Circle(pos.X, pos.Y, 6, 1, 0.2, 0.2, 1)
end)

utility.GetMenuState()

Returns whether the clover menu is currently open.

Returns:

  • boolean - true while the menu is visible, false while it is hidden.

Aliases: utility.get_menu_state, utility.getMenuState, clover.utility_get_menu_state

lua
cheat.register("onDraw", function()
    if not utility.GetMenuState() then
        draw.Text(10, 10, "menu hidden", 1, 1, 1, 1)
    end
end)

utility.GetClipboard()

Returns the text on the Windows clipboard.

Requires the unsafe API.

Returns:

  • string - The clipboard text, or an empty string when the clipboard holds no text or cannot be opened.

Aliases: utility.get_clipboard, utility.getClipboard, clover.utility_get_clipboard

  • Raises Clipboard access requires Allow unsafe Lua API. while the unsafe API is off.
  • The text is read in the ANSI CF_TEXT format, so characters outside the system code page may not come back intact.
lua
local ok, text = pcall(utility.GetClipboard)
if ok then
    print("clipboard has", #text, "bytes")
else
    warn(text)
end

utility.SetClipboard(text)

Replaces the clipboard contents with text in the ANSI CF_TEXT format. If the clipboard cannot be opened the call does nothing and reports no error.

Requires the unsafe API.

Parameters:

  • text: string - Text to copy. Numbers are converted to strings; other types raise an error.

Aliases: utility.set_clipboard, utility.setClipboard, clover.setclipboard

  • Raises Clipboard access requires Allow unsafe Lua API. while the unsafe API is off.
  • There is no global setclipboard or toclipboard. Use utility.SetClipboard or clover.setclipboard.
lua
local ok, err = pcall(utility.SetClipboard, "copied from clover")
if not ok then
    warn(err)
end

*

Keyboard and mouse helpers live on input. Per-frame callbacks such as the onDraw used above are covered on cheat.

input

Read and send keyboard and mouse input.

Keys are Windows virtual-key codes

keyboard.* and clover.* key functions take virtual-key codes such as 0x41 for A (see Key codes). For Enum.KeyCode, use UserInputService on Services.

Key state ignores focus

Key state is global, so it also reports keys pressed in other apps. Check isrbxactive first.

Sent input goes to the focused window

Senders (KeyPress, Click, MoveRel, ...) go to whichever window is focused, not necessarily Roblox.

toggle-clicker.lua
local enabled = false
local was_down = false
local next_click = 0

cheat.register("onStep", function()
    local down = keyboard.IsKeyDown(0x75)
    if down and not was_down then
        enabled = not enabled
        notify(enabled and "Clicker on" or "Clicker off")
    end
    was_down = down

    local now = utility.GetTickCount()
    if enabled and isrbxactive() and now >= next_click then
        mouse.Click()
        next_click = now + 100
    end
end)

Press F6 (0x75) to toggle. While Roblox is focused the script clicks roughly every 100 ms; the exact spacing depends on the script loop, which runs about every 16 ms (see cheat).

Keyboard

keyboard.IsKeyDown(vk)

Returns whether the key with virtual-key code vk is held down right now.

Parameters:

  • vk: integer - Virtual-key code. A fractional number or a non-numeric value raises an error.

Returns:

  • boolean - true while the key is down.

Aliases: keyboard.is_key_down, keyboard.isKeyDown, clover.is_key_down, clover.iskeypressed

  • clover.iskeypressed is this function and takes only integers. The global iskeypressed is a different function that also accepts names.
lua
if keyboard.IsKeyDown(0xA0) then
    print("left shift is held")
end

keyboard.KeyPress(vk)

Sends a key-down event for vk with SendInput. The key stays down for the receiving window until you send keyboard.KeyRelease.

Parameters:

  • vk: integer - Virtual-key code. The matching scan code is filled in automatically.

Returns:

  • boolean - Always true. It does not tell you whether Windows accepted the event.

Aliases: keyboard.key_press, keyboard.keyPress, clover.keypress

lua
task.spawn(function()
    keyboard.KeyPress(0x20)
    task.wait(0.05)
    keyboard.KeyRelease(0x20)
end)

keyboard.KeyRelease(vk)

Sends a key-up event for vk with SendInput.

Parameters:

  • vk: integer - Virtual-key code.

Returns:

  • boolean - Always true.

Aliases: keyboard.key_release, keyboard.keyRelease, clover.keyrelease

Mouse

mouse.IsButton1Down()

Returns whether the left mouse button is held down.

Returns:

  • boolean - true while the button is down.

Aliases: mouse.is_button1_down, mouse.isButton1Down, clover.ismouse1pressed

mouse.IsButton2Down()

Returns whether the right mouse button is held down.

Returns:

  • boolean - true while the button is down.

Aliases: mouse.is_button2_down, mouse.isButton2Down, clover.ismouse2pressed

mouse.Press()

Sends a left-button down event at the current cursor position. Returns nothing.

Aliases: mouse.press, clover.mouse1press

mouse.Release()

Sends a left-button up event. Returns nothing.

Aliases: mouse.release, clover.mouse1release

mouse.Click()

Sends a left-button down event immediately followed by an up event. Returns nothing.

Aliases: mouse.click, clover.mouse1click

mouse.RightPress()

Sends a right-button down event. Returns nothing.

Aliases: mouse.right_press, mouse.rightPress, clover.mouse2press

mouse.RightRelease()

Sends a right-button up event. Returns nothing.

Aliases: mouse.right_release, mouse.rightRelease, clover.mouse2release

mouse.RightClick()

Sends a right-button down event immediately followed by an up event. Returns nothing.

Aliases: mouse.right_click, mouse.rightClick, clover.mouse2click

mouse.MoveAbs(x, y)

Moves the cursor to (x, y) in pixels, measured from the top-left corner of the client area of the focused window, using SetCursorPos. Returns nothing.

Parameters:

  • x: integer - Horizontal position. Fractional numbers raise an error, so round with math.floor first.
  • y: integer - Vertical position, same rules as x.

Aliases: mouse.move_abs, mouse.moveAbs, clover.mousemoveabs

  • When no window has focus, x and y are screen coordinates.
lua
local w, h = cheat.GetWindowSize()
mouse.MoveAbs(math.floor(w / 2), math.floor(h / 2))

mouse.MoveRel(dx, dy)

Moves the cursor by (dx, dy) with a relative SendInput mouse-move event. Returns nothing.

Parameters:

  • dx: integer - Horizontal movement. Positive moves right.
  • dy: integer - Vertical movement. Positive moves down.

Aliases: mouse.move_rel, mouse.moveRel, clover.mousemoverel

  • Windows applies the pointer speed and acceleration settings to relative moves, so the cursor may not travel exactly dx by dy pixels.

mouse.Scroll(amount)

Sends a mouse-wheel event of amount notches (amount * 120 wheel units). Returns nothing.

Parameters:

  • amount: integer - Positive values scroll up (wheel away from you), negative values scroll down.

Aliases: mouse.scroll, clover.mousescroll

lua
mouse.Scroll(-3)

mouse.GetPosition()

The same function as utility.GetMousePos: the cursor position relative to the focused window's client area.

Returns:

  • table - { X = x, Y = y, x = x, y = y } with float values in pixels.

Aliases: mouse.get_position, mouse.getPosition, utility.GetMousePos, clover.utility_get_mouse_pos

Global helpers

getmousepos()

Returns the cursor position in pixels relative to the client area of the focused window, as two numbers. When no window has focus the values are screen coordinates.

Returns:

  • x: number - Horizontal position.
  • y: number - Vertical position.
  • Same position as mouse.GetPosition, which returns a table instead.
lua
local x, y = getmousepos()
print(string.format("cursor at %d, %d", x, y))

iskeypressed(key)

Returns whether a key or mouse button is held down. Unlike keyboard.IsKeyDown it also accepts a key name. Like the other readers it ignores focus.

Parameters:

  • key: integer | string - A virtual-key code, or a name from iskeypressed names. Other types raise an error.

Returns:

  • boolean - true while the key is down. false for an empty string or a code of 0 or less.
  • Unknown names use their first letter, so "F1" checks F. Use numeric codes for those keys.
  • "left" and "right" are the mouse buttons, not the arrow keys.
  • This is not clover.iskeypressed, which is keyboard.IsKeyDown and accepts only integers.
lua
if iskeypressed("shift") and iskeypressed("lmb") then
    print("shift-click")
end

isrbxactive()

Returns whether the focused window belongs to the Roblox process clover is attached to.

Returns:

  • boolean - false when another window is focused or no process is attached.

Aliases: clover.isrbxactive

lua
if isrbxactive() and keyboard.IsKeyDown(0x45) then
    print("E pressed in game")
end

Other raw functions

clover.setrobloxinput(enabled)

Accepted for compatibility with scripts that call it. The flag is stored, but nothing in the engine reads it, so the call has no effect on input. Returns nothing.

Parameters:

  • enabled: boolean - Any value; only its truthiness is stored.

Key codes

Common Windows virtual-key codes. Lua accepts the hex literals as written. The last column is the name the menu library's keybinds use for the same key (see ui); keyboard.* itself never takes names.

KeyCodeUI keybind name
Left mouse button0x01MB1
Right mouse button0x02MB2
Middle mouse button0x04MB3
Mouse button 40x05Mouse4
Mouse button 50x06Mouse5
Backspace0x08Backspace
Tab0x09Tab
Enter0x0DEnter
Shift (either)0x10Shift
Ctrl (either)0x11Ctrl
Alt (either)0x12Alt
Caps Lock0x14
Escape0x1BEscape
Space0x20Space
Page Up0x21PageUp
Page Down0x22PageDown
End0x23End
Home0x24Home
Left arrow0x25Left
Up arrow0x26Up
Right arrow0x27Right
Down arrow0x28Down
Insert0x2DInsert
Delete0x2EDelete
0 to 9 (top row)0x30 to 0x390 to 9
A to Z0x41 to 0x5AA to Z
Numpad 0 to 90x60 to 0x69
F1 to F120x70 to 0x7BF1 to F12
Left Shift0xA0LeftShift
Right Shift0xA1RightShift
Left Ctrl0xA2LeftControl
Right Ctrl0xA3RightControl
Left Alt0xA4LeftAlt
Right Alt0xA5RightAlt

iskeypressed names

The names the global iskeypressed understands. Matching is case-insensitive.

NameCodeKey
left, lmb0x01Left mouse button
right, rmb0x02Right mouse button
middle, mmb0x04Middle mouse button
tab0x09Tab
shift0x10Either Shift
ctrl, control0x11Either Ctrl
alt0x12Either Alt
escape, esc0x1BEscape
space0x20Space
insert0x2DInsert
delete0x2EDelete
any other stringfirst character, uppercasedOnly reliable for letters and digits: a gives 0x41, 7 gives 0x37

ui

Build windows with tabs, sections and elements, with change callbacks and saved configs.

Library means the Clover global

The library is the global Clover. This page writes Library for local Library = Clover. Call methods with a colon.

An open window takes the mouse and keyboard

While any window exists, the overlay captures mouse and keyboard. Call Window:Destroy() or Library:Destroy() to release them.

Callbacks run later and cannot yield

Callbacks run on the next script pass and can't yield; use task.spawn. Errors are logged as ui callback: warnings.

SetValue is silent

SetValue, .Value = x and LoadConfig never fire callbacks. Dropdowns, keybinds and color pickers don't update on screen.

menu.lua
local Library = Clover

local Window = Library:CreateWindow({
    Title = "Speed Tools",
    SubTitle = "v1.0",
    Size = { X = 520, Y = 400 },
})

local Main = Window:AddTab({ Title = "Main" })
local Movement = Main:AddSection("Movement")

local Enabled = Movement:AddToggle({
    Id = "SpeedEnabled",
    Title = "Enabled",
    Default = false,
    Callback = function(on)
        print("speed enabled:", on)
    end,
})

local Speed = Movement:AddSlider({
    Id = "SpeedValue",
    Title = "Speed",
    Min = 16,
    Max = 100,
    Default = 32,
    Rounding = 0,
})

Speed:OnChanged(function(value)
    print("speed:", value)
end)

Movement:AddButton({
    Title = "Save config",
    Callback = function()
        local ok = Library:SaveConfig("speed-tools")
        Library:Notify({ Content = ok and "Config saved" or "Save failed", Severity = ok and "success" or "error" })
    end,
})

Library:LoadConfig("speed-tools")
print(Enabled.Value, Speed.Value)

Library

Library:CreateWindow([options])

Creates a window near the top-right of the screen and returns it.

Parameters:

  • options: table (optional) - Window options. Keys other than the four below (for example Theme, Acrylic or TabWidth) are ignored.
  • options.Title: string (optional, default "clover") - Title bar text.
  • options.SubTitle: string (optional, default "") - Appended to the title after two spaces.
  • options.Size: table (optional, default { X = 580, Y = 460 }) - Size in pixels as { X = 580, Y = 460 }. A Vector2 is ignored.
  • options.MinSize: table (optional, default { X = 470, Y = 380 }) - Smallest size the user can resize the window to, in the same format as Size.

Returns:

  • Window - The new window. See Window.

Aliases: Clover:CreateWindow

lua
local Window = Clover:CreateWindow({
    Title = "My Script",
    SubTitle = "by me",
    Size = { 600, 480 },
    MinSize = { X = 500, Y = 400 },
})

Library:Notify([options])

Shows a Clover toast notification through clover.notify. Only one line of text is shown: Content when it is set, otherwise Title.

Parameters:

  • options: table (optional) - Notification options. Other keys, such as Duration, are ignored.
  • options.Content: string (optional) - Text to show. Passed through tostring.
  • options.Title: string (optional) - Shown only when Content is nil.
  • options.Severity: string (optional, default "success") - "success", "warning" or "error" (exact, lower case). Any other text is shown as success.

Aliases: Clover:Notify

lua
Clover:Notify({ Content = "Could not find the target", Severity = "warning" })

Library:SafeCallback(callback, ...)

Calls callback(...) and logs any error as a ui callback: warning instead of raising.

Parameters:

  • callback: function - Function to call.
  • ...: any (optional) - Arguments passed to callback.

Aliases: Clover:SafeCallback

lua
Clover:SafeCallback(function(a, b)
    print(a + b)
end, 1, 2)

Library:SaveConfig([name])

Saves every handle in Library.Options to configs/<name>.cfg in the workspace. See Configs.

Parameters:

  • name: string (optional, default "default") - Config name. It must match the Lua pattern ^[%w_.%-]+$ (letters, digits, _, . and -) and must not contain ...

Returns:

  • ok: boolean - true when the file was written; false when the name is invalid or the write was refused.
  • err: string - Only when the write itself failed: "file.write: refused path or unwritable file".

Aliases: Clover:SaveConfig

  • Everything in Library.Options is saved, including elements created by other scripts in the same VM.
  • Only booleans, numbers, strings and colors are saved.
lua
local ok, err = Clover:SaveConfig("legit")
if not ok then
    warn("save failed:", err or "invalid name")
end

Library:LoadConfig([name])

Loads configs/<name>.cfg into Library.Options. Create your elements first.

Parameters:

  • name: string (optional, default "default") - Config name, with the same rules as SaveConfig.

Returns:

  • boolean - false when the name is invalid or the file cannot be read; otherwise true.

Aliases: Clover:LoadConfig

  • Like every SetValue, loading never calls Callback or OnChanged handlers. Read the handles' Value afterwards to apply the loaded settings.
  • Dropdowns, keybinds and color pickers don't update on screen.
  • A value of the wrong type for its element, such as text on a slider line in a hand-edited file, raises an error out of LoadConfig.
lua
if Clover:LoadConfig("legit") then
    print("speed is", Clover.Options.SpeedValue.Value)
end

Library:Destroy()

Removes every script window from every script.

Aliases: Clover:Destroy

  • Old handles in Library.Options aren't cleared and may point at new elements. Don't reuse them.

Library:Minimize()

Does nothing. It exists so scripts written for other menu libraries do not error. Use Window:Minimize.

Aliases: Clover:Minimize

Library:Maximize()

Does nothing. It exists so scripts written for other menu libraries do not error. Use Window:Maximize.

Aliases: Clover:Maximize

Library.Options

Element handles keyed by their Id option. Shared by every script, so use unique ids.

Aliases: Clover.Options

lua
local speed = Clover.Options.SpeedValue
if speed then
    print(speed.Value)
end

Library.Loaded

true once the library has loaded. Library:Destroy() sets it to false, and nothing sets it back.

Returns:

  • boolean

Aliases: Clover.Loaded

Library.Unloaded

false until Library:Destroy() sets it to true. Handy as a stop flag in render loops. Window:Destroy() does not change it.

Returns:

  • boolean

Aliases: Clover.Unloaded

Library.Version

Library version string, currently "1.0.0".

Returns:

  • string

Aliases: Clover.Version

Window

Library:CreateWindow returns a Window object. Tabs show in a tab bar in the order they were added; a window without tabs shows (no tabs added yet).

Window:AddTab([options])

Adds a tab to the window and returns its Tab object.

Parameters:

  • options: table (optional) - Tab options. Pass a table: a plain string is read as a table without Title, so the tab would be called Tab.
  • options.Title: string (optional, default "Tab") - Text on the tab.
  • options.Icon: string (optional, default "") - Stored with the tab but not drawn.

Returns:

lua
local Main = Window:AddTab({ Title = "Main" })
local Settings = Window:AddTab({ Title = "Settings" })

Window:Dialog([options])

Opens a modal dialog with OK and optional Cancel buttons. Calling again replaces it.

Parameters:

  • options: table (optional) - Dialog options.
  • options.Title: string (optional, default "") - Heading line.
  • options.Content: string (optional, default "") - Body text, wrapped.
  • options.OkLabel: string (optional, default "OK") - OK button text. Confirm is read when OkLabel is nil.
  • options.CancelLabel: string (optional, default "") - Cancel button text. Cancel is read when CancelLabel is nil. When the result is empty there is no Cancel button.
  • options.OnOk: function (optional) - Called when OK is pressed. OnConfirm is read when OnOk is nil.
  • options.OnCancel: function (optional) - Called when Cancel is pressed.
  • Dialog callbacks are not wrapped in SafeCallback. An error in one is logged at error level as ui callback: <message>.
lua
Window:Dialog({
    Title = "Reset settings?",
    Content = "This puts every option back to its default.",
    OkLabel = "Reset",
    CancelLabel = "Cancel",
    OnOk = function()
        print("reset confirmed")
    end,
})

Window:Minimize()

Collapses the window to its title bar until Restore or Maximize.

Window:Maximize([on])

With true, pins the window to the top-left corner at the full screen size every frame and clears the minimized state. With false, returns it to its normal, user-resizable size.

Parameters:

  • on: boolean (optional, default true) - Whether to maximize.

Window:Restore()

Clears both the minimized and the maximized state.

Window:SetTheme([name])

Stores a theme name on the window. The renderer does not read it, so the window keeps its fixed dark colors.

Parameters:

  • name: string (optional, default "Dark") - Theme name, passed through tostring.

Window:Destroy()

Removes this window. Its tabs and elements stop drawing and never fire again. Other windows, Library.Options, Library.Loaded and Library.Unloaded are not touched.

Window fieldTypeDescription
_idnumberWindow id, usable with the clover.ui functions.
_tabstableArray of the Tab objects returned by AddTab, in creation order.

Tabs and sections

Tabs and sections both have the Add* methods (see Elements); only tabs have AddSection. Elements added to a tab are drawn above its sections.

Tab:AddSection([title])

Adds a section to the tab and returns its Section object. Sections cannot contain other sections.

Parameters:

  • title: string (optional, default "") - Section title. This is a plain string, not an options table: a table is passed through tostring and shows up as table: 0x....

Returns:

  • Section - The new section.
lua
local Main = Window:AddTab({ Title = "Main" })
local Aim = Main:AddSection("Aim")
local Visuals = Main:AddSection("Visuals")
Container fieldTypeDescription
Tab._idnumberTab id, usable as a parent id with clover.ui.
Section._idnumberSection id, usable as a parent id with clover.ui.

Elements

Every Add* method works on tabs and sections, takes one options table and returns an element handle. Callbacks never fire on creation.

Unique titles for buttons and toggles

Give buttons and toggles unique titles within a tab; duplicates interfere with each other.

Section:AddButton([options])

Adds a full-width button.

Parameters:

  • options.Id: string (optional) - Key under which the handle is stored in Library.Options.
  • options.Title: string (optional, default "") - Button text.
  • options.Description: string (optional, default "") - Help text under the button and in its tooltip.
  • options.Callback: function (optional) - Called with no arguments when the button is clicked.

Returns:

  • handle - Element handle. Its Value is always nil.

Aliases: Tab:AddButton

  • OnChanged on a button handle never fires on clicks; only Callback does.
lua
Section:AddButton({
    Title = "Rejoin",
    Description = "Prints a message for now",
    Callback = function()
        print("clicked")
    end,
})

Section:AddToggle([options])

Adds a checkbox.

Parameters:

  • options.Id: string (optional) - Key in Library.Options.
  • options.Title: string (optional, default "") - Label next to the checkbox.
  • options.Description: string (optional, default "") - Help text and tooltip.
  • options.Default: boolean (optional, default false) - Starting state. Any truthy value turns it on.
  • options.Callback: function (optional) - Called with the new state (true or false) when the user clicks it.

Returns:

  • handle - Element handle whose Value is the boolean state.

Aliases: Tab:AddToggle

lua
local Esp = Section:AddToggle({
    Id = "EspEnabled",
    Title = "ESP",
    Default = true,
    Callback = function(on)
        print("esp", on)
    end,
})

Section:AddSlider([options])

Adds a horizontal slider with the title to its right.

Parameters:

  • options.Id: string (optional) - Key in Library.Options.
  • options.Title: string (optional, default "") - Label.
  • options.Description: string (optional, default "") - Help text and tooltip.
  • options.Min: number (optional, default 0) - Left end of the slider.
  • options.Max: number (optional, default 100) - Right end of the slider.
  • options.Default: number (optional, default Min) - Starting value.
  • options.Rounding: number (optional, default 0) - Number of decimal places shown. Dragging also rounds the value to this precision, so 0 gives whole numbers. Negative values count as 0.
  • options.Callback: function (optional) - Called with the new number on every change while the user drags.

Returns:

  • handle - Element handle whose Value is the number.

Aliases: Tab:AddSlider

  • Values arrive as floats (32.0, and 0.1 as 0.10000000149011612). Use math.floor for integers.
lua
Section:AddSlider({
    Id = "AimFov",
    Title = "FOV",
    Min = 10,
    Max = 500,
    Default = 120,
    Rounding = 0,
    Callback = function(value)
        print("fov", math.floor(value))
    end,
})

Section:AddInput([options])

Adds a one-line text box.

Parameters:

  • options.Id: string (optional) - Key in Library.Options.
  • options.Title: string (optional, default "") - Label.
  • options.Description: string (optional, default "") - Help text and tooltip.
  • options.Placeholder: string (optional, default "") - Grey hint shown while the box is empty.
  • options.Default: string (optional, default "") - Starting text, passed through tostring.
  • options.Numeric: boolean (optional, default false) - When truthy only digits and . + - * / can be typed. The value is still a string.
  • options.MaxLength: number (optional, default 0) - Maximum length in bytes. 0 allows up to 4095.
  • options.Callback: function (optional) - Called with the full text on every edit (each keystroke). There is no option to wait for Enter.

Returns:

  • handle - Element handle whose Value is the text.

Aliases: Tab:AddInput

lua
Section:AddInput({
    Id = "TargetName",
    Title = "Target",
    Placeholder = "player name",
    MaxLength = 20,
    Callback = function(text)
        print("target:", text)
    end,
})

Section:AddParagraph([options])

Adds a title line followed by wrapped text. A paragraph has no description, callback or Id (it is never added to Library.Options).

Parameters:

  • options.Title: string (optional, default "") - First line. Omitted when empty.
  • options.Content: string (optional, default "") - Wrapped body text.

Returns:

  • handle - Element handle whose Value is the content, with an extra SetContent method.

Aliases: Tab:AddParagraph

lua
local Status = Section:AddParagraph({ Title = "Status", Content = "Idle" })
Status:SetContent("Running")

Section:AddDropdown([options])

Adds a drop-down list. In single-select mode it shows the selected option (or (none)); in multi-select mode it shows the ticked options joined with , and each option is a checkbox.

Parameters:

  • options.Id: string (optional) - Key in Library.Options.
  • options.Title: string (optional, default "Dropdown") - Label.
  • options.Description: string (optional, default "") - Help text and tooltip.
  • options.Options: table (optional, default {}) - Array of option strings (up to 256). Values also works.
  • options.Default: number | string | table (optional) - Single select: an index or name. Multi select: a table of names.
  • options.Multi: boolean (optional, default false) - Lets the user tick several options.
  • options.Searchable: boolean (optional, default false) - Adds a case-insensitive filter box.
  • options.SearchPlaceholder: string (optional, default "Search...") - Hint text of the filter box.
  • options.AllowNull: boolean (optional, default false) - Single select only: adds a (none) entry at the top of the list.
  • options.MaxItems: number (optional, default 6) - Accepted but not used.
  • options.Callback: function (optional) - Single select: the picked name. Multi select: the option just toggled.

Returns:

  • handle - Element handle. It also has SetValues.

Aliases: Tab:AddDropdown

  • Value starts as the first option regardless of Default. In multi-select it holds the last toggled name, not a list.
  • The options and the selection on screen cannot be changed after creation: SetValue and SetValues only change Lua-side fields.
lua
Section:AddDropdown({
    Id = "AimPart",
    Title = "Aim part",
    Options = { "Head", "Torso", "HumanoidRootPart" },
    Default = "Head",
    Searchable = true,
    Callback = function(part)
        print("aiming at", part)
    end,
})

Section:AddKeybind([options])

Adds a key-binding button. Click it, then press a key; Escape cancels and Delete clears.

Parameters:

  • options.Id: string (optional) - Key in Library.Options.
  • options.Title: string (optional, default "Keybind") - Label.
  • options.Description: string (optional, default "") - Help text and tooltip.
  • options.Default: string | number (optional) - A key name or virtual-key code. "1" means left mouse; use 49 for the 1 key.
  • options.Mode: string (optional, default "Toggle") - "Toggle", "Hold" or "Always". Only Toggle mode reacts to presses; Hold and Always never call Callback.
  • options.Callback: function (optional) - Toggle mode: called with true/false each time the key is pressed.
  • options.ChangedCallback: function (optional) - Called with the new key name when the user rebinds, or "" when cleared.

Returns:

Aliases: Tab:AddKeybind

  • Keys are only checked while the keybind's window is open and its tab is selected.
  • Value becomes the toggle state, not the key, so configs don't remember rebinds.
  • Escape and Delete cannot be bound by pressing them, but can be set by name through Default.
lua
Section:AddKeybind({
    Title = "Panic key",
    Default = "End",
    Mode = "Toggle",
    Callback = function(state)
        print("panic latch is now", state)
    end,
    ChangedCallback = function(name)
        print("rebound to", name)
    end,
})

Section:AddColorpicker([options])

Adds a color swatch with the title next to it. Clicking the swatch opens a color picker.

Parameters:

  • options.Id: string (optional) - Key in Library.Options.
  • options.Title: string (optional, default "Color") - Label.
  • options.Description: string (optional, default "") - Help text and tooltip.
  • options.Default: Color3 | table (optional, default { R = 1, G = 1, B = 1 }) - Starting color as { R, G, B } from 0 to 1. A Color3 works too.
  • options.Transparency: number (optional) - 0 to 1. When set, adds an alpha bar.
  • options.Callback: function (optional) - Called with { R, G, B } (0 to 1). Alpha is in the handle's alpha field.

Returns:

  • handle - Element handle whose Value starts as { R, G, B, A } and becomes the { R, G, B } table passed to Callback. It also has SetValueRGB and alpha.

Aliases: Tab:AddColorpicker

  • The method is spelled AddColorpicker; AddColorPicker does not exist.
  • Callback receives a table, not a Color3. Build one with Color3.new(c.R, c.G, c.B) when an API needs it.
  • OnChanged gets the packed number, not a color table. Use Callback for colors.
lua
local Tint = Section:AddColorpicker({
    Id = "EspColor",
    Title = "ESP color",
    Default = Color3.fromRGB(255, 80, 80),
    Transparency = 0.2,
    Callback = function(c)
        print(c.R, c.G, c.B, Tint.alpha)
    end,
})

Element handles

Every Add* method returns a handle table. The property and methods below exist on every handle; a few kinds add their own. The table shows how each kind behaves.

KindValue at creationValue after user inputSetValue changes the screenOnChanged fires
Buttonnilunchangednonever
ToggleDefault as a booleanbooleanyeson click
SliderDefault, or Minnumberyes (needs a number or numeric string)while dragging
InputDefault textstringyes (string or number required)on every edit
ParagraphContentunchangedyesnever
Dropdownfirst option, {} when Multipicked name, nil for (none)noon every pick
KeybindDefault, or ""latched boolean (Toggle mode)noon each press (Toggle mode)
Colorpicker{ R, G, B, A }{ R, G, B }noon change, with a packed number

Element.Value

The handle's cached value. Reading it is the same as Get(); assigning to it calls SetValue.

lua
local Esp = Clover.Options.EspEnabled
Esp.Value = not Esp.Value

Element:Get()

Returns the cached value (see the table above).

Returns:

  • any

Element:SetValue(value)

Sets the value without firing callbacks. Dropdowns, keybinds and color pickers only update the cached Value.

Parameters:

  • value: any - New value.

Element:OnChanged(callback)

Sets the change handler (replacing any previous one) and calls it once with the current value.

Parameters:

  • callback: function - Called through SafeCallback with the new value.
lua
Clover.Options.AimFov:OnChanged(function(value)
    print("fov changed to", value)
end)

Kind-specific members

Keybind:GetState()

Always false unless Mode is "Always". Track the state from Callback instead.

Returns:

  • boolean

Keybind:DoClick()

Calls the keybind's Callback with no arguments through SafeCallback. Neither Value nor the latch changes.

Keybind:OnClick(callback)

Same as OnChanged: the handler is called once right away with Value, then with the latched boolean on each press in Toggle mode.

Parameters:

  • callback: function - Handler.

Keybind.mode

The Mode option as given, "Toggle" by default.

Returns:

  • string

Keybind.toggled

Set to false at creation and never updated. See GetState.

Returns:

  • boolean

Colorpicker:SetValueRGB(color, [transparency])

Stores color as Value and sets alpha to 1 - transparency. The picker on screen does not change.

Parameters:

  • color: table | Color3 - Stored as is.
  • transparency: number (optional, default 0) - 0 to 1.

Colorpicker.alpha

Current alpha from 0 to 1. Starts at 1 - Transparency (1 without Transparency) and is updated from the picker on every change.

Returns:

  • number

Paragraph:SetContent(text)

Replaces the paragraph's text. Same as SetValue(tostring(text)).

Parameters:

  • text: any - New text, passed through tostring.

Dropdown:SetValues(list)

Stores list in the handle's _options field. The options shown on screen do not change.

Parameters:

  • list: table - Any value.

Internal fields

FieldTypeDescription
_idnumberElement id for the clover.ui functions. 0 when creation failed because the parent tab or section no longer existed.
_kindstring"button", "toggle", "slider", "input", "paragraph", "dropdown", "keybind" or "colorpicker".
_valueanyThe storage behind Value.
_optionsanyDropdown only, set by SetValues.

Reading the live value

clover.ui.get_value(handle._id) reads the value straight from the renderer for toggles, sliders, inputs and paragraphs. See clover.ui.get_value.

Configs

Library:SaveConfig(name) writes one id=value line for each entry in Library.Options to configs/<name>.cfg inside the workspace folder, in no particular order. Values are written like this:

  • booleans as true or false
  • numbers with tostring, so 32 or 32.0
  • strings in double quotes, with each " written as \"; backslashes and newlines are written as they are
  • tables with R, G and B as color(r,g,b,a) using %g formatting, with a set to 1 when the table has no A
  • anything else is left out
C:\clover\workspace\configs\speed-tools.cfg
SpeedEnabled=true
SpeedValue=48.0
AimPart="Head"
EspColor=color(1,0.313725,0.313725,1)

LoadConfig parses each line back into a boolean, string, color or number and applies it with SetValue, so callbacks don't fire.

Two configs folders

Script configs live in C:\clover\workspace\configs\, inside the file sandbox. They are separate from Clover's own settings in C:\clover\configs\.

clover.ui (raw)

The low-level C functions behind the ui library. Everything is addressed by integer id.

Prefer the high-level library

Most scripts should use Clover instead.

Ids

Create functions return an id, or 0 on failure (the reason is logged, see Error codes). Ids restart at 1 after destroy_all or a VM reset.

Option tables

Option keys are lowercase. A value of the wrong type silently falls back to the default.

Callbacks

Callbacks run on the next script pass and can't yield. Errors are logged and the callback stays connected. Open windows take mouse and keyboard focus.

raw-menu.lua
local ui = clover.ui

local win = ui.create_window({ title = "Raw demo", size_x = 420, size_y = 320 })
local tab = ui.add_tab(win, "Main")
local sec = ui.add_section(tab, "Movement")

local speed = ui.add_slider(sec, {
    title = "Speed",
    min = 0,
    max = 50,
    default = 16,
    callback = function(value)
        print("speed", value)
    end,
})

local enabled = ui.add_toggle(sec, { title = "Enabled" })
ui.connect_changed(enabled, function(on)
    print("enabled", on, "speed", ui.get_value(speed))
end)

ui.add_button(sec, {
    title = "Reset speed",
    callback = function()
        ui.set_value(speed, 16)
    end,
})

Windows

clover.ui.create_window(options)

Creates a window near the top-right of the screen and returns its id.

Parameters:

  • options: table - Required, even if empty.
  • options.title: string (optional, default "clover") - Title bar text.
  • options.subtitle: string (optional, default "") - Appended to the title after two spaces.
  • options.size_x: number (optional, default 580) - Initial width in pixels.
  • options.size_y: number (optional, default 460) - Initial height in pixels.
  • options.min_x: number (optional, default 470) - Minimum width while not maximized.
  • options.min_y: number (optional, default 380) - Minimum height while not maximized.

Returns:

  • window_id: integer - The new window's id.
lua
local win = clover.ui.create_window({ title = "Tools", subtitle = "beta", size_x = 500 })

clover.ui.destroy_window(window_id)

Removes the window from the screen. Window:Destroy() calls this.

Parameters:

  • window_id: integer - Window to remove.

clover.ui.destroy_all()

Removes every window and element from every script and restarts ids at 1. Library:Destroy() calls this.

clover.ui.window_set_minimized(window_id, [minimized])

Collapses the window to its title bar, or expands it again.

Parameters:

  • window_id: integer - Target window.
  • minimized: boolean (optional, default false) - Read for truthiness.

clover.ui.window_set_maximized(window_id, [maximized])

Pins the window to full screen, or returns it to normal size.

Parameters:

  • window_id: integer - Target window.
  • maximized: boolean (optional, default false) - Read for truthiness.

clover.ui.window_open_dialog(window_id, options)

Opens a modal dialog with OK and optional Cancel buttons. Calling again replaces it.

Parameters:

  • window_id: integer - Target window.
  • options: table - Dialog options.
  • options.title: string (optional, default "") - Heading line.
  • options.content: string (optional, default "") - Body text.
  • options.ok_label: string (optional, default "OK") - OK button text.
  • options.cancel_label: string (optional, default "") - Cancel button text. Empty means no Cancel button.
  • options.ok: function (optional) - Called when OK is pressed.
  • options.cancel: function (optional) - Called when Cancel is pressed.
lua
clover.ui.window_open_dialog(win, {
    title = "Unload?",
    content = "This closes the menu.",
    ok_label = "Unload",
    cancel_label = "Keep",
    ok = function()
        clover.ui.destroy_window(win)
    end,
})

clover.ui.window_set_theme(window_id, [theme])

Stores a theme name on the window. The renderer does not read it; windows always use a fixed dark palette.

Parameters:

  • window_id: integer - Target window.
  • theme: string (optional, default "Dark") - Theme name.

Tabs and sections

A window shows its tabs in a tab bar in creation order and draws only the selected tab. Inside it, elements added straight to the tab come first, then each section as a separator line with its title followed by its elements.

clover.ui.add_tab(window_id, [title], [icon])

Adds a tab to a window. An unknown window logs [clover error 10]: add_tab: window N and returns 0.

Parameters:

  • window_id: integer - Parent window.
  • title: string (optional, default "Tab") - Tab text.
  • icon: string (optional, default "") - Stored but not drawn.

Returns:

  • tab_id: integer - New tab id, or 0.

clover.ui.add_section(tab_id, [title])

Adds a section to a tab. An unknown tab logs [clover error 11]: add_section: tab N and returns 0.

Parameters:

  • tab_id: integer - Parent tab.
  • title: string (optional, default "") - Text in the separator line.

Returns:

  • section_id: integer - New section id, or 0.

Elements

Each add_* takes a parent (tab or section) id and an options table, and returns the element id or 0 for an unknown parent. description shows as grey text and a tooltip.

clover.ui.add_button(parent_id, options)

Adds a full-width button. Its title is its ImGui ID, so keep button titles unique within a tab.

Parameters:

  • parent_id: integer - Section or tab id.
  • options.title: string (optional, default "Button") - Button text.
  • options.description: string (optional, default "") - Help text and tooltip.
  • options.callback: function (optional) - Called with no arguments on click.

Returns:

  • element_id: integer

clover.ui.add_toggle(parent_id, options)

Adds a checkbox. Its title is its ImGui ID, so keep toggle titles unique within a tab.

Parameters:

  • parent_id: integer - Section or tab id.
  • options.title: string (optional, default "Toggle") - Label.
  • options.description: string (optional, default "") - Help text and tooltip.
  • options.default: boolean (optional, default false) - Starting state. Must be a real boolean.
  • options.callback: function (optional) - Called with the new boolean when the user clicks it.

Returns:

  • element_id: integer

clover.ui.add_slider(parent_id, options)

Adds a float slider. The value is kept as a 32-bit float and passed to callbacks as a Lua float.

Parameters:

  • parent_id: integer - Section or tab id.
  • options.title: string (optional, default "Slider") - Label.
  • options.description: string (optional, default "") - Help text and tooltip.
  • options.min: number (optional, default 0) - Left end.
  • options.max: number (optional, default 100) - Right end.
  • options.default: number (optional, default min) - Starting value.
  • options.rounding: number (optional, default 0) - Decimal places shown (truncated to an integer; negative counts as 0). Dragging rounds the value to the same precision.
  • options.callback: function (optional) - Called with the new number on every change while dragging.

Returns:

  • element_id: integer

clover.ui.add_input(parent_id, options)

Adds a one-line text box.

Parameters:

  • parent_id: integer - Section or tab id.
  • options.title: string (optional, default "Input") - Label.
  • options.description: string (optional, default "") - Help text and tooltip.
  • options.placeholder: string (optional, default "") - Hint shown while empty.
  • options.default: string (optional, default "") - Starting text.
  • options.numeric: boolean (optional, default false) - Only digits and . + - * / can be typed.
  • options.max_length: number (optional, default 0) - Maximum length in bytes; 0 means 4095.
  • options.callback: function (optional) - Called with the full text (a string) on every edit.

Returns:

  • element_id: integer

clover.ui.add_paragraph(parent_id, options)

Adds a title line and wrapped text. Paragraphs have no description or callback.

Parameters:

  • parent_id: integer - Section or tab id.
  • options.title: string (optional, default "") - First line; omitted when empty.
  • options.content: string (optional, default "") - Body text. Change it later with set_value.

Returns:

  • element_id: integer

clover.ui.add_dropdown(parent_id, options)

Adds a single- or multi-select drop-down list. The options and selection cannot be changed after creation.

Parameters:

  • parent_id: integer - Section or tab id.
  • options.title: string (optional, default "Dropdown") - Label.
  • options.description: string (optional, default "") - Help text and tooltip.
  • options.options: table (optional, default {}) - Array of option names. At most 256 are read; non-strings are converted with tostring. Not a table means no options.
  • options.default: integer | string | table (optional) - Single select: a 1-based index or option name. Multi select: a table of names. Otherwise the first option, unless allow_null.
  • options.multi: boolean (optional, default false) - Checkbox per option.
  • options.searchable: boolean (optional, default false) - Adds a case-insensitive filter box (up to 63 bytes) inside the open list.
  • options.search_placeholder: string (optional, default "Search...") - Hint of the filter box.
  • options.allow_null: boolean (optional, default false) - Single select: adds a (none) entry.
  • options.max_items: number (optional, default 6) - Stored but not used.
  • options.callback: function (optional) - Single select: the picked name (nothing for (none)). Multi select: the option just toggled.

Returns:

  • element_id: integer

clover.ui.add_keybind(parent_id, options)

Adds a key-binding button. Click it, then press a key; Escape cancels and Delete clears.

Parameters:

  • parent_id: integer - Section or tab id.
  • options.title: string (optional, default "Keybind") - Label.
  • options.description: string (optional, default "") - Help text and tooltip.
  • options.default: number | string (optional) - A virtual-key code or a key name. "1" means code 1 (left mouse), not the 1 key.
  • options.mode: string (optional, default "Toggle") - "Always" or "always", "Hold" or "hold"; anything else is Toggle. Only Toggle mode fires callback.
  • options.callback: function (optional) - Toggle mode: each time the key goes down an internal latch flips and this is called with its new boolean value.
  • options.changed_callback: function (optional) - Called with the new key name after the user rebinds, or with "" after Delete or when the key has no name in the table.

Returns:

  • element_id: integer

clover.ui.add_colorpicker(parent_id, options)

Adds a color picker. Callbacks get the color packed as R * 16777216 + G * 65536 + B * 256 + A (0-255 each).

Parameters:

  • parent_id: integer - Section or tab id.
  • options.title: string (optional, default "Color") - Label.
  • options.description: string (optional, default "") - Help text and tooltip.
  • options.default_r: number (optional, default 1) - Red, 0 to 1.
  • options.default_g: number (optional, default 1) - Green, 0 to 1.
  • options.default_b: number (optional, default 1) - Blue, 0 to 1.
  • options.transparency: number (optional) - 0 (opaque) to 1. When present the picker gets an alpha bar starting at 1 - transparency; when absent alpha is fixed at 1.
  • options.callback: function (optional) - Called with the packed number whenever the color changes.

Returns:

  • element_id: integer
lua
local function unpack_rgba(packed)
    local p = math.floor(packed)
    return p // 16777216 % 256, p // 65536 % 256, p // 256 % 256, p % 256
end

clover.ui.add_colorpicker(sec, {
    title = "Tint",
    default_r = 1,
    default_g = 0.5,
    default_b = 0,
    callback = function(packed)
        local r, g, b, a = unpack_rgba(packed)
        print(r, g, b, a)
    end,
})

Values and events

clover.ui.set_value(element_id, value)

Changes what an element shows. Never fires callbacks. Unknown ids are ignored.

Parameters:

  • element_id: integer - Target element.
  • value: any - Toggle: truthiness. Slider: a number. Input and paragraph: text. Ignored for other elements.

clover.ui.get_value(element_id)

Reads the value the renderer currently holds.

Parameters:

  • element_id: integer - Target element.

Returns:

  • boolean | number | string | nil - Toggle: boolean. Slider: number. Input: text. Paragraph: content. Buttons, dropdowns, keybinds, color pickers and unknown ids: nil.

clover.ui.connect_changed(element_id, callback)

Sets the element's single change handler, replacing any previous one.

Parameters:

  • element_id: integer - Target element.
  • callback: function - Handler. Anything else raises an error.

Callback arguments

SourceArgumentsWhen
button callbacknoneclick
togglebooleanclick
slidernumberevery change while dragging
inputstring (full text)every edit
dropdown, single selectoption name, or none for (none)pick
dropdown, multi selectname of the toggled optiontick or untick
keybindboolean latchbound key goes down, Toggle mode only
keybind changed_callbackkey name, or ""rebind or clear
colorpickerpacked RGBA numbercolor change
dialog ok / cancelnonebutton click

Error codes

CodeLogged byMessage
10add_tab, window_open_dialog[clover error 10]: add_tab: window N or [clover error 10]: dialog: window N
11add_section[clover error 11]: add_section: tab N
13every add_* element function[clover error 13]: add_slider: parent N, with the calling function's name
15VM startA built-in Lua prelude failed to load (the one that defines Clover, or the stdlib one); the Lua error follows.

Key names

Key names accepted by a keybind's default, case-insensitive.

NameKeyCode
A to ZLetters0x41 to 0x5A
0 to 9Number row0x30 to 0x39. Pass the code: the strings "0" to "9" are numeric, so default reads them as codes 0 to 9.
F1 to F12Function keys0x70 to 0x7B
LeftControl, RightControlLeft and right Ctrl0xA2, 0xA3
LeftShift, RightShiftLeft and right Shift0xA0, 0xA1
LeftAlt, RightAltLeft and right Alt0xA4, 0xA5
Ctrl, Shift, AltEither Ctrl, Shift or Alt0x11, 0x10, 0x12
SpaceSpace bar0x20
EnterEnter0x0D
TabTab0x09
BackspaceBackspace0x08
End, HomeEnd, Home0x23, 0x24
Insert, DeleteInsert, Delete0x2D, 0x2E
PageUp, PageDownPage Up, Page Down0x21, 0x22
Left, Up, Right, DownArrow keys0x25, 0x26, 0x27, 0x28
MB1, MB2, MB3Left, right and middle mouse button0x01, 0x02, 0x04
Mouse4, Mouse5Side mouse buttons0x05, 0x06
EscapeEsc0x1B

cheat

Register per-frame callbacks and read the Roblox viewport size.

Event names are exact

Only onDraw, onFrame, onPulse and onStep work. Any other name, including onPaint and onTick, is silently ignored.

Tick and render callbacks share one loop

Each pass (about every 16 ms) runs tick callbacks (onPulse, onStep, clover.on_tick), then render callbacks (onDraw, onFrame, clover.on_render). Draw again every pass; see draw.

An error removes the callback

A callback that errors is logged and removed. Callbacks cannot yield, so task.wait inside one errors; use task.spawn instead.

watermark.lua
local w, h = cheat.GetWindowSize()

cheat.register("onStep", function()
    w, h = cheat.GetWindowSize()
end)

cheat.register("onDraw", function()
    if w == 0 then
        return
    end
    local label = "clover"
    local tw, th = draw.GetTextSize(label)
    draw.RectangleFilled(w - tw - 20, 10, w - 10, th + 18, 0, 0, 0, 0.6, 4)
    draw.Text(w - tw - 15, 14, label, 1, 1, 1, 1)
end)

Events

EventPhaseSame as
onPulsetickclover.on_tick(callback)
onSteptickclover.on_tick(callback)
onDrawrenderclover.on_render(callback)
onFramerenderclover.on_render(callback)

Both names of a pair add to the same list. Within a phase, callbacks run in the order they were registered.

Loop order

One iteration of the script loop, while the master switch is on:

  1. Scripts you queued for execution run.
  2. Tick callbacks run.
  3. Render callbacks run.
  4. The engine pumps events: game events, task timers and wait resumes, tweens, RunService and UserInputService signals, and WebSocket messages.
  5. Callbacks from ui elements that changed run.
  6. The draw commands from this iteration are published to the overlay.
  7. The thread sleeps 16 ms.

Nothing runs while the master switch is off. Callbacks get no arguments; for elapsed time use RunService.Heartbeat (see Signals). A VM reset drops all callbacks.

Functions

cheat.register(event, callback)

Adds callback to the tick or render list chosen by event. Returns nothing.

Parameters:

  • event: string - onPulse/onStep (tick) or onDraw/onFrame (render). Other names are ignored.
  • callback: function - Called with no arguments on every loop iteration. Return values are ignored.

Aliases: cheat.Register, clover.cheat_register

  • Raises cheat.register: expected function as argument 2 when callback is not a function.
  • Registering the same function twice makes it run twice per iteration.
  • There is no function to unregister a callback. Guard the body with a flag of your own if it needs to stop.
lua
local enabled = true

cheat.register("onDraw", function()
    if not enabled then
        return
    end
    draw.Text(20, 20, "hello from onDraw", 1, 1, 1, 1)
end)

cheat.GetWindowSize()

Returns the width and height of the Roblox viewport in pixels, read from the game's render engine. Can be called anywhere, not only in callbacks.

Returns:

  • width: number - Viewport width, or 0 when it cannot be read (for example, not attached yet).
  • height: number - Viewport height, or 0 when it cannot be read.

Aliases: cheat.get_window_size, cheat.getWindowSize, clover.cheat_get_window_size

lua
local w, h = cheat.GetWindowSize()
print("viewport", w, h)

clover.on_tick(callback)

Adds callback to the tick list. Same as cheat.register("onPulse", callback). Returns nothing.

Parameters:

  • callback: function - Called with no arguments on every loop iteration, before the render callbacks.
  • Raises expected function as argument 1 when callback is not a function.
lua
local ticks = 0

clover.on_tick(function()
    ticks = ticks + 1
end)

clover.on_render(callback)

Adds callback to the render list. Same as cheat.register("onDraw", callback). Returns nothing.

Parameters:

  • callback: function - Called with no arguments on every loop iteration, after the tick callbacks.
  • Raises expected function as argument 1 when callback is not a function.
lua
clover.on_render(function()
    clover.draw_text(20, 40, "hello from on_render", 1, 0.8, 0.2, 1)
end)

file

Read and write files inside the clover workspace folder.

Everything lives in the workspace

Paths are relative to C:\clover\workspace. Absolute paths and .. are refused. No unsafe API needed. See Path rules.

Two error styles

file.Read/file.Write return nil/false plus a message on failure. The globals (readfile, writefile, ...) raise errors instead, so use pcall.

16 MiB per file

Reads fail for files larger than 16 MiB (16777216 bytes) and writes fail for data larger than that. appendfile fails when the combined size would exceed it.

settings.lua
local path = "myscript/settings.json"
local settings = { esp = true, fov = 90 }

if isfile(path) then
    local ok, data = pcall(function()
        return HttpService:JSONDecode(readfile(path))
    end)
    if ok and type(data) == "table" then
        settings = data
    end
end

settings.runs = (settings.runs or 0) + 1
writefile(path, HttpService:JSONEncode(settings))
print("saved", path, "run", settings.runs)

writefile creates the myscript folder on the first run. JSON helpers are on http.

file table

file.Read(path)

Reads a whole file. Binary-safe: the string holds the exact bytes.

Parameters:

  • path: string - Workspace-relative path. A trailing slash makes it invalid.

Returns:

  • data: string - The file contents on success.
  • nil - On failure nil is returned together with the message file.read: refused path or unreadable file. This covers a refused path, a missing file, a folder and a file over 16 MiB.

Aliases: file.read, clover.file_read

lua
local text, err = file.Read("notes.txt")
if text then
    print(#text, "bytes")
else
    warn(err)
end

file.Write(path, data)

Creates or overwrites a file with data. Missing parent folders are created.

Parameters:

  • path: string - Workspace-relative path. A trailing slash makes it invalid.
  • data: string - New contents, binary-safe. Numbers are converted to strings; other types raise an error.

Returns:

  • boolean - true on success. On failure false is returned together with the message file.write: refused path or unwritable file.

Aliases: file.write, clover.file_write

lua
local ok, err = file.Write("logs/session.txt", "started")
if not ok then
    warn(err)
end

file.Exists(path)

Returns whether path names an existing regular file. Folders return false; use isfolder for those.

Parameters:

  • path: string - Workspace-relative path.

Returns:

  • boolean - true for an existing file, false for folders, missing files and refused paths.

Aliases: file.exists, file.IsFile, file.isFile, clover.file_exists

file.Delete(path)

Deletes a file. Folders cannot be deleted.

Parameters:

  • path: string - Workspace-relative path.

Returns:

  • boolean - true when the file was deleted, false otherwise.

Aliases: file.delete, clover.file_delete

Global functions

These globals are also on the clover table (clover.readfile and so on). Unlike the file table they strip trailing / and \ from the path first, so "logs/" means "logs".

readfile(path)

Reads a whole file. Binary-safe.

Parameters:

  • path: string - Workspace-relative path.

Returns:

  • string - The file contents.

Aliases: clover.readfile

  • Raises readfile: the file does not exist or the path is not allowed for a refused path, a missing file, a folder or a file over 16 MiB.
lua
local ok, text = pcall(readfile, "notes.txt")
print(ok and text or "no notes yet")

writefile(path, data)

Creates or overwrites a file with data. Missing parent folders are created. Returns nothing.

Parameters:

  • path: string - Workspace-relative path.
  • data: string - New contents, binary-safe. Numbers are converted to strings.

Aliases: clover.writefile

  • Raises writefile: the path is not allowed or the file cannot be written on failure, including data over 16 MiB.
lua
writefile("myscript/last-run.txt", os.date("%Y-%m-%d %H:%M:%S"))

appendfile(path, data)

Adds data to the end of a file, creating the file (and missing parent folders) when it does not exist. The existing contents are read and the whole file is written again. Returns nothing.

Parameters:

  • path: string - Workspace-relative path.
  • data: string - Text or bytes to append. Numbers are converted to strings.

Aliases: clover.appendfile

  • Raises an error if the path is refused, the file can't be written, or the result would exceed 16 MiB.
lua
appendfile("myscript/log.txt", os.date("%H:%M:%S") .. " joined\n")

isfile(path)

Returns whether path names an existing regular file. Same check as file.Exists, after trailing slashes are stripped.

Parameters:

  • path: string - Workspace-relative path.

Returns:

  • boolean - true for an existing file, false for folders, missing files and refused paths.

Aliases: clover.isfile

isfolder(path)

Returns whether path names an existing folder.

Parameters:

  • path: string - Workspace-relative path.

Returns:

  • boolean - true for an existing folder, false for files, missing folders and refused paths.

Aliases: clover.isfolder

makefolder(path)

Creates a folder together with any missing parent folders. Succeeds without changes when the folder already exists. Returns nothing.

Parameters:

  • path: string - Workspace-relative folder path.

Aliases: clover.makefolder

  • Raises makefolder: the path is not allowed or the folder cannot be created on failure, for example when a file already has that name.
lua
if not isfolder("myscript/cache") then
    makefolder("myscript/cache")
end

delfile(path)

Deletes a file. Returns nothing.

Parameters:

  • path: string - Workspace-relative path of a file.

Aliases: clover.delfile

  • Raises delfile: the file does not exist or the path is not allowed on failure, including when path is a folder.
  • There is no delfolder: folders cannot be deleted from Lua.
lua
if isfile("myscript/old.txt") then
    delfile("myscript/old.txt")
end

listfiles(path)

Lists the files and folders directly inside a folder. Pass "" to list the workspace root.

Parameters:

  • path: string - Workspace-relative folder path, or "" for the root. Required.

Returns:

  • table - An array of paths that can be passed straight to the other functions: path .. "/" .. name for each entry, or just the name when listing the root. Folders have no trailing slash. The order is whatever Windows returns.

Aliases: clover.listfiles

  • At most 10000 entries are returned. Symbolic links, junctions and names that break the path rules are skipped.
  • Raises listfiles: the folder does not exist or the path is not allowed on failure.
lua
for _, entry in ipairs(listfiles("")) do
    if isfolder(entry) then
        print("folder", entry)
    else
        print("file", entry)
    end
end

Path rules

Every function checks the path the same way before touching the disk. A path that breaks a rule is refused: the file table returns nil/false, isfile/isfolder return false and the other globals raise an error.

  • Paths are relative to the workspace root. Both / and \ work as separators.
  • Absolute paths are refused: no drive letter (C:...) and no leading / or \.
  • The path must not be empty, must be valid UTF-8, must not contain a NUL byte and must be at most 4096 bytes long.
  • No segment may be . or .., so neither ../x nor ./x works.
  • No segment may end in a dot or a space, or contain :, *, ?, ", <, >, | or a control character.
  • A trailing slash leaves an empty last segment, which the file table refuses. The globals strip trailing slashes before checking.
  • Windows device names are refused as a segment, compared case-insensitively against the part before the first dot: CON, PRN, AUX, NUL, CONIN$, CONOUT$, CLOCK$, COM0 to COM9 and LPT0 to LPT9. So nul.txt is refused too.
  • Symbolic links and junctions are refused anywhere along the path, and so are files with more than one hard link.

Limits

LimitValue
File size for reads and writes16 MiB (16777216 bytes)
Path length4096 bytes
Entries returned by listfiles10000

loadfile and dofile

loadfile(path) and dofile(path) are Lua shims built on readfile, so they follow the same path rules. loadfile returns the compiled chunk, or nil and a message; dofile runs the file and raises on failure. Details are on Executor Compatibility.

The menu library's SaveConfig and LoadConfig also store their files here, under configs/. See ui.

task

Sleep, spawn, delay and defer coroutines.

Waiting needs a coroutine

Only top-level code and functions started by task.spawn/delay/defer can wait. Engine callbacks (signals, render, UI) can't; spawn a task inside them.

Extra arguments are dropped

Extra arguments after the function are ignored. Capture values in a closure.

Time advances in ticks

Ticks are about 16 ms, so timing is accurate to roughly one tick.

task-demo.lua
local loop = task.spawn(function()
    while true do
        print("tick", task.wait(1))
    end
end)

local reminder
reminder = task.delay(10, function()
    reminder = nil
    notify("ten seconds passed")
end)

task.defer(function()
    print("runs on the next scheduler pass")
end)

task.wait(3)
task.cancel(loop)
if reminder then
    reminder:Cancel()
    reminder = nil
end
print("loop stopped, reminder cancelled")

How scheduling works

Every script shares one Lua state that runs on a single script thread. That thread repeats the steps below, then sleeps about 16 ms. dt is the real time in seconds since the previous tick started. Nothing runs while the master switch is off.

  1. Scripts queued since the last tick start, each in a new coroutine, and run until they finish or first yield.
  2. clover.on_tick callbacks run, then clover.on_render callbacks (see cheat).
  3. game and workspace are refreshed if the DataModel changed, and polled game events fire: PlayerAdded, PlayerRemoving, CharacterAdded, ChildAdded, ChildRemoved and DescendantAdded.
  4. dt is subtracted from every sleeping thread's remaining time. Threads that reach zero resume, and their wait returns dt.
  5. Due task.delay and task.defer functions start, each in a new coroutine.
  6. Playing tweens write their new values (see TweenService).
  7. RunService.Heartbeat, RenderStepped and Stepped fire with dt, and threads waiting on them resume.
  8. UserInputService.InputBegan and InputEnded fire for key and mouse button changes.
  9. WebSocket OnMessage and OnClose events fire.
  10. Queued UI element callbacks run and the frame's draw list is published.

Code that runs without yielding holds the script thread: no other script, callback or signal runs until it yields or returns. A VM reset or turning the master switch off interrupts it. A VM reset also drops every sleeping thread, pending delay, signal connection and tween.

Where you can yield

Code running inCan yield
The top-level script chunkYes
Functions started by task.spawn, spawn or SpawnYes
Functions started by task.delay, delay, Delay or task.deferYes
Tween.Completed and game.Loaded handlers (the engine starts them with task.spawn)Yes
Engine signal handlers (:Connect and :Once on RunService, UserInputService, Instance, Player and WebSocket signals), including RunService:BindToRenderStep and ContextActionService:BindAction callbacksNo
clover.on_tick, clover.on_render and cheat.register callbacksNo
UI element callbacksNo

Functions

The globals wait and spawn are the same C functions as task.wait and task.spawn. Wait, Spawn, delay and Delay are defined by the standard prelude on top of the task functions. There is no global defer or cancel.

task.wait([seconds])

Pauses the current coroutine for seconds.

Yields.

Parameters:

  • seconds: number (optional, default 0.03) - Time to sleep, in seconds.

Returns:

  • dt: number - Length in seconds of the tick that resumed the thread. This is not the total time slept.

Aliases: wait, Wait

lua
for i = 3, 1, -1 do
    print(i)
    task.wait(1)
end
print("go")

task.spawn(callback)

Runs callback in a new coroutine right away. Use it for loops that wait, or to wait from a callback.

Parameters:

  • callback: function - Called with no arguments. Coroutines are not accepted.

Returns:

  • thread: thread - The new coroutine. Pass it to task.cancel to stop it while it sleeps.

Aliases: spawn, Spawn

  • Arguments after callback are ignored.
  • An error inside the coroutine, on the first run or after a later resume, is caught and logged as task: <message>. It never reaches the caller.
lua
UserInputService.InputBegan:Connect(function(input)
    if input.KeyCode == Enum.KeyCode.G then
        task.spawn(function()
            print("charging")
            task.wait(2)
            print("released")
        end)
    end
end)

task.delay(seconds, callback)

Schedules callback to start in a new coroutine once seconds of scheduler time have passed. The coroutine may yield.

Parameters:

  • seconds: number - Delay in seconds. Required; negative values count as 0.
  • callback: function - Called with no arguments.

Returns:

  • handle: Task - A Task handle that can cancel the call before it runs.

Aliases: delay, Delay

  • Arguments after callback are ignored.
  • Errors in callback are logged as task: <message>.
lua
local hint
hint = task.delay(5, function()
    hint = nil
    notify("Press F to open the menu")
end)

UserInputService.InputBegan:Connect(function(input)
    if hint and input.KeyCode == Enum.KeyCode.F then
        hint:Cancel()
        hint = nil
    end
end)

task.defer(callback)

Queues callback with a delay of zero. It starts in a new coroutine the next time the scheduler runs due delays, which is within one tick, and never before task.defer returns.

Parameters:

  • callback: function - Called with no arguments.

Returns:

  • Arguments after callback are ignored.
  • Errors in callback are logged as task: <message>.
lua
task.defer(function()
    print("second")
end)
print("first")

task.cancel(target)

Cancels a pending task.delay/task.defer handle, or a coroutine sleeping in task.wait or WaitForChild.

Parameters:

  • target: Task | thread - A handle or a coroutine. Any other value is ignored.
  • Returns nothing and never raises an error for a wrong type.
  • A coroutine that is running, finished, or waiting on a signal's :Wait() is not affected.
  • The coroutine is not closed; it simply stays suspended.
lua
local counter = task.spawn(function()
    local n = 0
    while true do
        n = n + 1
        print("count", n)
        task.wait(0.5)
    end
end)

task.wait(2)
task.cancel(counter)
print("counter stopped")

Task handle

task.delay and task.defer return a Task handle, a userdata for which typeof returns "Task". It has one method.

Cancel only while pending

Cancelling a handle twice, or after it ran, can cancel an unrelated delay. Drop the reference once it's done.

Task:Cancel()

Same as task.cancel(handle): removes the pending call so it never runs. Returns nothing.

lua
local pending = task.delay(3, function()
    print("this never prints")
end)
pending:Cancel()
pending = nil

Signals

Event objects with Connect, Once, Wait and Fire, and every signal the engine exposes.

Handlers cannot yield

Handlers can't yield. Use task.spawn for anything that waits (see task).

A handler that raises an error is disconnected

The error is logged as signal: <message> and that connection is removed, so the handler is never called again. Wrap risky code in pcall if the handler must survive errors.

Call methods with a colon

Connect, Once and Fire read their arguments from position 2. signal:Connect(fn) works; signal.Connect(fn) raises an error and signal.Fire(a) drops a.

signals-demo.lua
local elapsed = 0
local heartbeat = RunService.Heartbeat:Connect(function(dt)
    elapsed = elapsed + dt
end)

local Players = game:GetService("Players")
Players.PlayerAdded:Connect(function(player)
    print("joined:", player.Name)
end)

UserInputService.InputBegan:Connect(function(input, gameProcessed)
    if input.KeyCode == Enum.KeyCode.F then
        print("F pressed after", elapsed, "seconds")
    end
end)

local dt = RunService.Heartbeat:Wait()
print("first tick took", dt)

task.wait(10)
heartbeat:Disconnect()
heartbeat = nil

Signal object

Engine signals are plain tables holding the four functions below, so typeof returns "table" for them. Instance signals such as part.ChildAdded build a new table every time you read them; connections made through any copy behave the same.

Signal:Connect(callback)

Registers callback. Each time the signal fires, the handler is called with the signal's arguments. Handlers run one after another on the script thread, in no guaranteed order.

Parameters:

  • callback: function - The handler. Raises an error if this is not a function.

Returns:

  • connection: RBXScriptConnection - Use it to disconnect the handler.
  • Handlers connected during a fire run from the next fire.
  • Connections last until they are disconnected or the VM resets; they do not end when the script that made them finishes.
lua
local total = 0
RunService.Heartbeat:Connect(function(dt)
    total = total + dt
end)

Signal:Once(callback)

Like Connect, but the connection is removed just before the handler's first call.

Parameters:

  • callback: function - The handler.

Returns:

  • connection: RBXScriptConnection - Can disconnect the handler before it runs.
lua
UserInputService.InputBegan:Once(function(input)
    print("first input:", input.KeyCode, input.UserInputType)
end)

Signal:Wait()

Suspends the running coroutine until the signal next fires, then returns the values it fired with.

Yields.

Returns:

  • ...any - The fire's arguments, for example dt for RunService.Heartbeat.
  • Raises an error when the calling code cannot yield.
  • There is no timeout. The coroutine stays suspended until the next fire or a VM reset, and task.cancel does not remove it.
lua
local character = game:GetService("Players").LocalPlayer.CharacterAdded:Wait()
print("respawned as", character.Name)

Signal:Fire(...)

Fires the signal from Lua. Every matching handler is called with the given values and every coroutine in :Wait() resumes with them, all before Fire returns.

Parameters:

  • ...: any (optional) - Values passed to the handlers.
  • RunService.Heartbeat, RenderStepped, Stepped and their aliases are one event, so firing any of them runs the handlers of all of them.
  • A property-changed signal only reaches handlers for the same instance and property name.
  • This reaches handlers inside this engine only. It does not fire the real Roblox event; for that see firesignal on the Remotes page.
lua
local storage = game:GetService("ReplicatedStorage")
local remote = storage and storage:FindFirstChild("Notify")
if remote and remote:IsA("RemoteEvent") then
    remote.OnClientEvent:Connect(function(message)
        print("handler got", message)
    end)
    remote.OnClientEvent:Fire("test message")
end

Connection object

Connect and Once on engine signals return an RBXScriptConnection userdata (typeof returns "RBXScriptConnection"). It has a single method and no Connected property: reading connection.Connected returns nil.

Disconnect once

Disconnected slots are reused, so calling Disconnect twice can disconnect a newer connection. Clear your reference after disconnecting.

RBXScriptConnection:Disconnect()

Stops the handler from being called again and releases it. Returns nothing.

lua
local conn
conn = RunService.Heartbeat:Connect(function()
    if UserInputService:IsKeyDown(Enum.KeyCode.Escape) then
        conn:Disconnect()
        conn = nil
    end
end)

Lua-side signals

A few signals are written in Lua instead of being engine signals. They use the same method names but behave differently:

SignalConnect and Once returnHandler errorsWaitFire
UserInputService.InputChanged, TextBoxFocused, TextBoxFocusReleased, WindowFocused, WindowFocusReleasedA table with Connected and DisconnectCaught by pcall and silently ignored; the handler stays connectedPolls with task.wait() until the next Fire, then returns its values:Fire(...) calls handlers in connection order. The engine never fires these
Tween.CompletedA table with Connected and DisconnectReported with warn as Tween completion: <message>; handlers run in their own coroutine and may yieldYields until the tween completes or is cancelled and returns the PlaybackState; raises an error outside a coroutineNot available
game.LoadedA table whose Connected is false and whose Disconnect does nothingThe handler starts at once in task.spawn, so errors are logged as task: <message>Returns immediately with no valuesNot available

Connection.Connected

true until disconnected. Only on Lua-side signals; engine connections don't have it.

Returns:

  • boolean

Signal reference

Every signal the engine exposes, with the arguments handlers receive in order. Game events are found by comparing snapshots once per tick, so a change that comes and goes within one tick is not reported.

SignalFires whenArgumentsNotes
RunService.HeartbeatOnce per scheduler tick, after sleeping threads, delays and tweens are processeddt: number, seconds since the previous tickAlias RunService.PostSimulation
RunService.RenderSteppedAt the same moment as Heartbeat; it is the same eventdtAlias RunService.PreRender. Not tied to Roblox render frames
RunService.SteppedAt the same moment as HeartbeatdtAliases RunService.PreAnimation and RunService.PreSimulation. Roblox passes (time, dt); here only dt
UserInputService.InputBeganA key with a KeyCode mapping, or mouse button 1, 2 or 3, goes down while Roblox is the foreground windowinput: InputObject, gameProcessed: always falseKeys already held when the VM starts do not fire
UserInputService.InputEndedA key or button whose press was delivered is releasedinput, gameProcessed (false)Fires even if Roblox lost focus after the press
UserInputService.InputChanged, TextBoxFocused, TextBoxFocusReleased, WindowFocused, WindowFocusReleasedOnly when your own code calls :Fire(...)Whatever is passed to FireLua-side signals
Players.PlayerAddedA new Player appears under Playersplayer: InstancePlayers already present when the VM starts or the place changes do not fire
Players.PlayerRemovingA Player is no longer under Playersplayer: InstanceDetected after removal, so the instance may no longer be readable
Player.CharacterAddedThat player's character changes to a different, non-nil modelcharacter: InstanceEach player has its own signal
Instance.ChildAddedA new direct child appearschild: InstanceTracking starts the first time you read ChildAdded, ChildRemoved or DescendantAdded on that instance
Instance.ChildRemovedA direct child disappearschild: InstanceSame tracking as ChildAdded
Instance.DescendantAddedA new direct child appears; deeper descendants are not reporteddescendant: InstanceFires together with ChildAdded
Instance:GetPropertyChangedSignal(name)This engine assigns the property, or SetAttribute is called with that nameNoneChanges made by the game itself are not seen; see below
game.LoadedLua-side: Connect and Once run the handler immediatelyNonegame.Loaded:Wait() returns at once
BindableEvent.Eventbindable:Fire(...) or bindable.Event:Fire(...) is called from LuaThe values passed to FireFires from Roblox scripts are not seen
RemoteEvent.OnClientEventOnly when your own code calls :Fire(...)The values passed to FireAlso on UnreliableRemoteEvent. Messages from the server are not delivered
ProximityPrompt.Triggeredprompt:Fire(...) or prompt.Triggered:Fire(...) is called from LuaThe values passed to Firefireproximityprompt triggers the real prompt but does not fire this signal
Tween.CompletedA tween finishes or is cancelledplaybackState: Enum.PlaybackState.Completed or CancelledSee TweenService
socket.OnMessageA message arrives on a WebSocketdata: string, isBinary: booleanSee WebSocket
socket.OnCloseThe WebSocket closescode: integer, reason: stringSee WebSocket

Other events do not exist

Events not listed here, like Humanoid.Died or Touched, don't exist.

Property-changed signals

Instance:GetPropertyChangedSignal(property)

Returns a signal that fires, with no arguments, right after this engine assigns property on this instance. It fires on every such assignment, even when the value did not change.

Parameters:

  • property: string - Property or attribute name.

Returns:

  • Signal - An engine signal bound to this instance and name.
  • Any instance: Parent (only when the reparent succeeds) and Value.
  • BasePart classes: Position, CFrame, Size, Transparency, Velocity, AssemblyLinearVelocity, Anchored, CanCollide, Color, Color3.
  • Humanoid: Health, MaxHealth, WalkSpeed, JumpPower, JumpHeight.
  • Lighting: Brightness, ClockTime, FogStart, FogEnd, GlobalShadows, Ambient, OutdoorAmbient, FogColor.
  • Camera: FieldOfView.
  • Any name passed to instance:SetAttribute(name, value) on the same instance, including when the value is nil.
  • Writes that the game makes on its own are never detected.
lua
local character = game:GetService("Players").LocalPlayer.Character
local humanoid = character and character:FindFirstChild("Humanoid")
if humanoid then
    humanoid:GetPropertyChangedSignal("WalkSpeed"):Connect(function()
        print("WalkSpeed set to", humanoid.WalkSpeed)
    end)
    humanoid.WalkSpeed = 24
end

BindableEvent and ProximityPrompt

BindableEvent:Fire(...)

Fires the instance's Event (or Triggered) for your own handlers. Roblox scripts aren't notified.

Parameters:

  • ...: any (optional) - Values passed to the handlers.

Aliases: ProximityPrompt:Fire

  • Raises Fire: unsupported instance on any other class.
lua
local storage = game:GetService("ReplicatedStorage")
local bindable = storage and storage:FindFirstChildOfClass("BindableEvent")
if bindable then
    bindable.Event:Connect(function(a, b)
        print("got", a, b)
    end)
    bindable:Fire("hello", 42)
end

Enum

Roblox-style enumerations used by input, RunService and TweenService.

clover only defines the enums listed on this page. Indexing a group or item that does not exist returns nil instead of raising an error, so check for typos when a comparison never matches.

enums.lua
UserInputService.InputBegan:Connect(function(input)
    if input.KeyCode == Enum.KeyCode.F then
        print("pressed", input.KeyCode.Name, input.KeyCode.Value)
    end
end)

print(tostring(Enum.KeyCode.Space))
print(Enum.KeyCode:FromValue(97))
print(#Enum.UserInputType:GetEnumItems())

EnumItem

Every item is a unique table, so compare items with ==. Items of the groups built by the standard library (all groups except the three tween groups) also convert to strings like Enum.KeyCode.Space.

EnumItem.Name

The item name, for example "Space".

Returns:

  • string

EnumItem.Value

The numeric value. KeyCode values follow Roblox, not Windows virtual-key codes.

Returns:

  • integer

EnumItem.EnumType

The group table the item belongs to, for example Enum.KeyCode. Not set on the tween enums.

Returns:

  • table

Enum groups

Groups built by the standard library answer these three lookups. Call them with a colon. tostring(Enum.KeyCode) returns "KeyCode".

Enum.<Group>:GetEnumItems()

Returns a new array with every item of the group, in definition order.

Returns:

  • {EnumItem}

Enum.<Group>:FromValue(value)

Looks an item up by its numeric value. When two names share a value, the one defined last wins.

Parameters:

  • value: integer

Returns:

  • EnumItem? - The item, or nil.

Enum.<Group>:FromName(name)

Looks an item up by name. Same as Enum.<Group>[name].

Parameters:

  • name: string

Returns:

  • EnumItem? - The item, or nil.

KeyCode

Windows VK is the key code clover polls. Keys marked none exist only for compatibility and are never reported. See Services.

NameValueWindows VK
A ... Z97 ... 12265 ... 90
Zero ... Nine48 ... 5748 ... 57
KeypadZero ... KeypadNine256 ... 26596 ... 105
F1 ... F15282 ... 296112 ... 126
Unknown0none
Backspace88
Tab99
Clear12none
Return1313
Pause1919
Escape2727
Space3232
QuotedDouble34none
Hash35none
Dollar36none
Percent37none
Ampersand38none
Quote39222
LeftParenthesis40none
RightParenthesis41none
Asterisk42none
Plus43none
Comma44188
Minus45189
Period46190
Slash47191
Colon58none
Semicolon59186
LessThan60none
Equals61187
GreaterThan62none
Question63none
At64none
LeftBracket91219
BackSlash92220
RightBracket93221
Caret94none
Underscore95none
Backquote96192
LeftCurly123none
Pipe124none
RightCurly125none
Tilde126none
Delete12746
KeypadPeriod266110
KeypadDivide267111
KeypadMultiply268106
KeypadMinus269109
KeypadPlus270107
KeypadEnter271none
KeypadEquals272none
Up27338
Down27440
Right27539
Left27637
Insert27745
Home27836
End27935
PageUp28033
PageDown28134
NumLock300144
CapsLock30120
ScrollLock302145
RightShift303161
LeftShift304160
RightControl305163
LeftControl306162
RightAlt307165
LeftAlt308164
RightMeta309none
LeftMeta310none
LeftSuper31191
RightSuper31292
Mode313none
Compose314none
Help315none
Print31644
SysReq317none
Break318none
Menu31993
Power320none
Euro321none
Undo322none

UserInputType

NameValueNotes
MouseButton10Left button, reported from VK 1.
MouseButton21Right button, reported from VK 2.
MouseButton32Middle button, reported from VK 4.
MouseWheel3Defined only.
MouseMovement4Defined only.
Touch7Defined only.
Keyboard8Type of every keyboard input object.
Focus9Defined only.
Accelerometer10Defined only.
Gyro11Defined only.
Gamepad112Defined only.
TextInput14Defined only.
InputMethod15Defined only.
None16Defined only.

Other groups

GroupItems (value)
UserInputStateBegin (0), Change (1), End (2), Cancel (3), None (4)
MouseBehaviorDefault (0), LockCenter (1), LockCurrentPosition (2)
RenderPriorityFirst (0), Input (100), Camera (200), Character (300), Last (2000)
RaycastFilterTypeExclude (0), Include (1), Blacklist (0), Whitelist (1)
ContextActionResultPass (1), Sink (0)

Tween enums

Created by TweenService. Items are plain tables with Name and Value only, without the group methods. TweenService also accepts names as strings, like "Quad".

GroupItems, value 0 first
EasingStyleLinear, Sine, Back, Quad, Quart, Quint, Bounce, Elastic, Exponential, Circular, Cubic
EasingDirectionIn, Out, InOut
PlaybackStateBegin, Delayed, Playing, Paused, Completed, Cancelled

Vector2

A 2D vector, used for screen positions and sizes.

Use Vector2.new, not Vector2(...)

Vector2(...) always errors. Use Vector2.new.

Smaller than the Roblox Vector2

Only new, zero, one, X/x, Y/y, Magnitude, Unit and arithmetic exist. No methods like Dot or Lerp.

mouse-ring.lua
local UserInputService = game:GetService("UserInputService")
local RunService = game:GetService("RunService")

local ring = Drawing.new("Circle")
ring.Radius = 60
ring.Thickness = 1
ring.Color = Color3.fromRGB(255, 255, 255)

local line = Drawing.new("Line")
line.Color = Color3.fromRGB(255, 80, 80)
line.Thickness = 2

RunService.RenderStepped:Connect(function()
    local width, height = clover.screen_size()
    local center = Vector2.new(width / 2, height / 2)
    local mouse = UserInputService:GetMouseLocation()
    local delta = mouse - center
    ring.Position = mouse
    line.From = center
    line.To = center + delta.Unit * math.min(delta.Magnitude, 200)
    line.Visible = delta.Magnitude > ring.Radius
end)

Constructors

Vector2.new([x], [y])

Creates a new Vector2 from two numbers. Missing or nil components are 0.

Parameters:

  • x: number (optional, default 0) - Horizontal component. Numeric strings such as "5" are converted; any other non-nil value raises number expected.
  • y: number (optional, default 0) - Vertical component, same rules as x.

Returns:

  • Vector2 - A new vector. Components are stored as 32-bit floats.
  • Can't copy a vector: Vector2.new(v) raises an error.
lua
local size = Vector2.new(200, 40)
local origin = Vector2.new()
print(size, origin, Vector2.new(7))

Constants

Constants are plain fields of the global Vector2 table, created once when the script state starts. Every read returns the same immutable object.

Vector2.zero

The vector 0, 0.

Returns:

  • Vector2

Vector2.one

The vector 1, 1.

Returns:

  • Vector2

Properties

Vector2.X

The horizontal component. It always comes back as a Lua float (math.type gives float), so print(Vector2.new(3, 4).X) prints 3.0.

Read-only.

Returns:

  • number

Aliases: Vector2.x

Vector2.Y

The vertical component, returned as a Lua float.

Read-only.

Returns:

  • number

Aliases: Vector2.y

Vector2.Magnitude

The length of the vector, sqrt(X * X + Y * Y), computed in single precision.

Read-only.

Returns:

  • number
lua
local v = Vector2.new(3, 4)
print(v.X, v.y, v.Magnitude)

Vector2.Unit

The same direction with length 1, or 0, 0 for a zero vector.

Read-only.

Returns:

  • Vector2
lua
print(Vector2.new(3, 4).Unit, Vector2.zero.Unit)

Operators

ExpressionResultBehavior
a + bVector2Component-wise sum. Both operands must be Vector2: v + 1 raises clover.Vector2 expected, got number.
a - bVector2Component-wise difference, with the same operand rule as +.
v * n, n * vVector2Scales both components by the number n. Multiplying two vectors raises number expected.
v / nVector2Divides both components by n. n / v raises an error. Dividing by 0 gives inf or nan components instead of an error.
-vVector2Negates both components.
a == bbooleanTrue when both components are exactly equal as 32-bit floats. Comparing with a different userdata type, such as a Vector3, raises an error; comparing with nil, a number or a table is just false.
tostring(v)stringThe components formatted with %g (6 significant digits) and joined by a comma and space, for example 0.333333, 1e+10. print(v) uses the same text.
typeof(v)stringVector2. Plain type(v) returns userdata.

No other operator is defined. //, %, ^, <, # and .. raise the usual Lua errors, so call tostring(v) before concatenating a vector into a string.

Storage

  • Each Vector2 is a full userdata holding two 32-bit floats, with the metatable named clover.Vector2. That name is what appears in error messages, and typeof strips the clover. prefix.
  • Components are rounded to single precision: Vector2.new(0.1, 0).X reads back as 0.10000000149012. Compare results of arithmetic with a tolerance instead of ==.
  • Values are immutable. There is no __newindex, so v.X = 5 raises attempt to index a clover.Vector2 value. Build a new vector instead.
  • Every constructor call and operator allocates a new userdata. Two equal vectors are still different table keys, so t[Vector2.new(1, 2)] does not find an entry stored under another Vector2.new(1, 2).

Where Vector2 is used

  • Drawing objects return Vector2 for Line From and To, Position on Text, Circle and Square, Square Size and the read-only Text TextBounds. Setting From, To, Position or Square Size accepts a Vector2 or a plain table with X and Y (or x and y) fields, where a missing field counts as 0.
  • UserInputService:GetMouseLocation() and UserInputService:GetMouseDelta() return Vector2 values built from the mouse position (see services).
  • Remote calls can send Vector2 arguments and receive Vector2 results (see remotes).
  • TweenService can tween Vector2 values (see services).

Vector3

A 3D vector for positions, sizes and directions.

Use Vector3.new, not Vector3(...)

Vector3(...) always errors. Use Vector3.new.

Member names and what exists

X, Y, Z are case-insensitive; Magnitude, Unit, Dot, Cross and Lerp aren't. Max, Min, Abs and similar don't exist.

distance-to-players.lua
local Players = game:GetService("Players")
local me = Players.LocalPlayer

local function rootOf(player)
    local character = player.Character
    return character and character:FindFirstChild("HumanoidRootPart")
end

local myRoot = me and rootOf(me)
if myRoot then
    for _, player in ipairs(Players:GetPlayers()) do
        local root = rootOf(player)
        if player ~= me and root then
            local delta = root.Position - myRoot.Position
            local flat = Vector3.new(delta.X, 0, delta.Z)
            print(player.Name, math.floor(delta.Magnitude), "studs, direction", flat.Unit)
        end
    end
end

Constructors

Vector3.new([x], [y], [z])

Creates a new Vector3 from three numbers. Missing or nil components are 0.

If the first argument is already a Vector3, it returns a copy of that vector and ignores the other arguments.

Parameters:

  • x: number | Vector3 (optional, default 0) - X component, or a Vector3 to copy. Numeric strings such as "5" are converted; any other non-nil value raises number expected.
  • y: number (optional, default 0) - Y component, same rules as x.
  • z: number (optional, default 0) - Z component, same rules as x.

Returns:

  • Vector3 - A new vector. Components are stored as 32-bit floats.
lua
local a = Vector3.new(1, 2, 3)
local b = Vector3.new(a)
print(a == b, rawequal(a, b), Vector3.new(5))

Constants

Constants are plain fields of the global Vector3 table, created once when the script state starts. Every read returns the same immutable object.

Vector3.zero

The vector 0, 0, 0.

Returns:

  • Vector3

Vector3.one

The vector 1, 1, 1.

Returns:

  • Vector3

Vector3.xAxis

The unit vector 1, 0, 0.

Returns:

  • Vector3

Vector3.yAxis

The unit vector 0, 1, 0, which is up in Roblox world space.

Returns:

  • Vector3

Vector3.zAxis

The unit vector 0, 0, 1.

Returns:

  • Vector3

Properties

Vector3.X

The X component. It always comes back as a Lua float (math.type gives float), so print(Vector3.new(1, 2, 3).X) prints 1.0.

Read-only.

Returns:

  • number

Aliases: Vector3.x

Vector3.Y

The Y component, returned as a Lua float.

Read-only.

Returns:

  • number

Aliases: Vector3.y

Vector3.Z

The Z component, returned as a Lua float.

Read-only.

Returns:

  • number

Aliases: Vector3.z

Vector3.Magnitude

The length of the vector, sqrt(X * X + Y * Y + Z * Z), computed in single precision.

Read-only.

Returns:

  • number
lua
local a = Vector3.new(0, 0, 0)
local b = Vector3.new(3, 4, 0)
print((b - a).Magnitude)

Vector3.Unit

The same direction with length 1, or 0, 0, 0 for a zero vector.

Read-only.

Returns:

  • Vector3

Methods

Methods are only reachable through a vector, as v:Dot(w) or v.Dot(v, w). Vector3.Dot on the global table is nil. Every argument named other or goal must be a Vector3; anything else, including a Vector2, raises clover.Vector3 expected.

Vector3:Dot(other)

Returns the dot product X * other.X + Y * other.Y + Z * other.Z.

Parameters:

  • other: Vector3 - The second vector.

Returns:

  • number - The dot product. For two unit vectors it is the cosine of the angle between them.
lua
local look = Vector3.new(0, 0, -1)
local toTarget = Vector3.new(3, 0, -4).Unit
print(look:Dot(toTarget))

Vector3:Cross(other)

Returns the cross product, a vector perpendicular to both inputs. It follows the right-hand rule: Vector3.xAxis:Cross(Vector3.yAxis) is 0, 0, 1.

Parameters:

  • other: Vector3 - The second vector.

Returns:

  • Vector3 - Y * other.Z - Z * other.Y, Z * other.X - X * other.Z, X * other.Y - Y * other.X.

Vector3:Lerp(goal, alpha)

Linear interpolation: returns self + (goal - self) * alpha.

Parameters:

  • goal: Vector3 - The vector reached at alpha = 1.
  • alpha: number - Required. It is not clamped, so values below 0 or above 1 extrapolate past the endpoints.

Returns:

  • Vector3 - The interpolated vector.
lua
local a = Vector3.new(0, 0, 0)
local b = Vector3.new(10, 0, 0)
print(a:Lerp(b, 0.25), a:Lerp(b, 2))

Operators

ExpressionResultBehavior
a + bVector3Component-wise sum. Both operands must be Vector3: v + 1 raises clover.Vector3 expected, got number.
a - bVector3Component-wise difference, with the same operand rule as +.
v * n, n * vVector3Scales every component by the number n. Multiplying two vectors raises number expected; use Dot or Cross instead.
v / nVector3Divides every component by n. n / v raises an error. Dividing by 0 gives inf or nan components instead of an error.
-vVector3Negates every component.
cf * vVector3A CFrame on the left transforms the vector from the frame's local space into world space. The reverse order, v * cf, raises number expected.
a == bbooleanTrue when all three components are exactly equal as 32-bit floats. Comparing with a different userdata type, such as a Vector2, raises an error; comparing with nil, a number or a table is just false.
tostring(v)stringThe components formatted with %g (6 significant digits) and joined by a comma and space, for example 1.23457e+08, 1e-07, 0.333333. print(v) uses the same text.
typeof(v)stringVector3. Plain type(v) returns userdata.

No other operator is defined. //, %, ^, <, # and .. raise the usual Lua errors, so call tostring(v) before concatenating a vector into a string.

Storage

  • Each Vector3 is a full userdata holding three 32-bit floats, with the metatable named clover.Vector3. That name is what appears in error messages, and typeof strips the clover. prefix.
  • Components are rounded to single precision: Vector3.new(0.1).X reads back as 0.10000000149012. Compare results of arithmetic with a tolerance, for example (a - b).Magnitude < 1e-4, instead of ==.
  • Values are immutable. There is no __newindex, so v.X = 5 raises attempt to index a clover.Vector3 value. Build a new vector instead.
  • Every constructor call and operator allocates a new userdata. Two equal vectors are still different table keys, so t[Vector3.new(1, 2, 3)] does not find an entry stored under another Vector3.new(1, 2, 3).

Where Vector3 is used

  • Instance properties: Position, Size, Velocity (or AssemblyLinearVelocity) and AssemblyAngularVelocity on parts, and Value on a Vector3Value. Assigning Position, Size, Velocity, AssemblyLinearVelocity or a Vector3Value's Value accepts a Vector3 or a plain table with X, Y and Z (or lowercase) fields, where a missing field counts as 0.
  • CFrame Position, LookVector, RightVector and UpVector are Vector3 values, and CFrame.new accepts a Vector3 position.
  • Input objects passed to UserInputService.InputBegan and InputEnded handlers carry Position as Vector3.new(mouseX, mouseY, 0) and a zero Delta (see services).
  • SetAttribute accepts a Vector3 value (see attributes).
  • Remote calls can send Vector3 arguments and receive Vector3 results (see remotes), and TweenService can tween Vector3 values (see services).
  • To put a world position on screen, pass its components to world_to_screen: world_to_screen(p.X, p.Y, p.Z).

Color3

Color3 is an immutable RGB color stored as three 32-bit floats, where each channel normally runs from 0 to 1.

Use Color3.new, not Color3(...)

Color3(...) always errors. Use Color3.new or Color3.fromRGB.

No == and no arithmetic

There is no __eq or arithmetic: two equal colors are not ==. Compare R, G, B or ToRGB.

The draw.* functions do not take Color3

draw functions reject Color3; pass a { R, G, B, A } table instead. Drawing objects accept Color3.

Ranges and missing members

new takes 0-1, fromRGB takes 0-255. Neither clamps. fromHSV, fromHex, Lerp, ToHSV and ToHex don't exist.

health-color.lua
local Players = game:GetService("Players")
local player = Players.LocalPlayer
local character = player and player.Character
local humanoid = character and character:FindFirstChildOfClass("Humanoid")

local function healthColor(fraction)
    fraction = math.max(0, math.min(1, fraction))
    return Color3.new(1 - fraction, fraction, 0)
end

if humanoid and humanoid.MaxHealth > 0 then
    local color = healthColor(humanoid.Health / humanoid.MaxHealth)
    local label = Drawing.new("Text")
    label.Text = ("HP %d"):format(math.floor(humanoid.Health))
    label.Position = Vector2.new(20, 20)
    label.Size = 18
    label.Outline = true
    label.Color = color
    print("health color", color:ToRGB())
end

Constructors

Color3.new([r], [g], [b])

Creates a color from channels in the 0-1 range. Missing or nil channels are 0, so Color3.new() is black.

Parameters:

  • r: number (optional, default 0) - Red, normally 0-1. Not clamped. Numeric strings are converted; any other non-nil value raises number expected.
  • g: number (optional, default 0) - Green, same rules as r.
  • b: number (optional, default 0) - Blue, same rules as r.

Returns:

  • Color3 - A new color. Channels are stored as 32-bit floats.
lua
local red = Color3.new(1, 0, 0)
local grey = Color3.new(0.5, 0.5, 0.5)
print(red, grey, Color3.new())

Color3.fromRGB([r], [g], [b])

Creates a color from 0-255 channels. Not clamped or rounded; missing channels are 0.

Parameters:

  • r: number (optional, default 0) - Red, normally 0-255.
  • g: number (optional, default 0) - Green, normally 0-255.
  • b: number (optional, default 0) - Blue, normally 0-255.

Returns:

  • Color3 - A new color whose channels are the inputs divided by 255.
lua
local orange = Color3.fromRGB(255, 128, 0)
print(orange.R, orange.G, orange.B)

Properties

Color3.R

The red channel as a Lua float, normally 0-1.

Read-only.

Returns:

  • number

Aliases: Color3.r

Color3.G

The green channel as a Lua float, normally 0-1.

Read-only.

Returns:

  • number

Aliases: Color3.g

Color3.B

The blue channel as a Lua float, normally 0-1.

Read-only.

Returns:

  • number

Aliases: Color3.b

Methods

Color3:ToRGB()

Returns the channels as 0-255 integers, rounded but not clamped.

Returns:

  • r: integer - Red, normally 0-255.
  • g: integer - Green, normally 0-255.
  • b: integer - Blue, normally 0-255.
  • The name is case-sensitive (c:torgb() fails), and the method is only reachable through a color: Color3.ToRGB on the global table is nil.
lua
local r, g, b = Color3.new(0.75, 0.25, 0):ToRGB()
print(r, g, b)

Operators

ExpressionResultBehavior
tostring(c)stringThe 0-1 channels formatted with %g (6 significant digits) and joined by a comma and space. Color3.fromRGB(10, 20, 30) prints as 0.0392157, 0.0784314, 0.117647.
typeof(c)stringColor3. Plain type(c) returns userdata.
a == bbooleanNo __eq: true only when both sides are the same object.
+ - * /, unary -errorNot defined. Build a new color from the channels instead.

Storage

  • Each Color3 is a full userdata holding three 32-bit floats (R, G, B, in that order), with the metatable named clover.Color3. That name is what appears in error messages, and typeof strips the clover. prefix.
  • Channels are rounded to single precision, so Color3.new(0.1).R reads back as 0.10000000149012.
  • Values are immutable. There is no __newindex, so c.R = 1 raises attempt to index a clover.Color3 value. Build a new color instead.
  • R, G and B are case-insensitive (c.r works). ToRGB is case-sensitive. Any other key reads as nil.

Where Color3 is used

  • Instance properties: Color (or its other name Color3) on parts, and Ambient, OutdoorAmbient and FogColor on Lighting. Assigning them accepts a Color3 or a plain table with R, G and B (or lowercase) fields, where a missing field counts as 0.
  • Drawing objects return a Color3 for Color. Setting Color accepts a Color3 or the same kind of table.
  • Remote calls can send Color3 arguments and receive Color3 results (see remotes), and TweenService can tween Color3 values (see services).

CFrame

A position plus a 3x3 rotation matrix, stored as twelve floats.

A minimal CFrame

Only CFrame.new, Position/p, X, Y, Z, LookVector, RightVector, UpVector and * exist. Everything else (Angles, lookAt, Inverse, Lerp, ToObjectSpace, ...) reads as nil. See the recipes for replacements.

Use CFrame.new, not CFrame(...)

CFrame(...) always errors. Use CFrame.new.

Case-sensitive members

Member names are case-sensitive: cf.x is nil.

facing-line.lua
local Players = game:GetService("Players")
local RunService = game:GetService("RunService")

local line = Drawing.new("Line")
line.Color = Color3.fromRGB(80, 200, 255)
line.Thickness = 2

RunService.RenderStepped:Connect(function()
    local player = Players.LocalPlayer
    local character = player and player.Character
    local root = character and character:FindFirstChild("HumanoidRootPart")
    if not root then
        line.Visible = false
        return
    end
    local cf = root.CFrame
    local tip = cf * Vector3.new(0, 0, -10)
    local x1, y1, visible1 = world_to_screen(cf.X, cf.Y, cf.Z)
    local x2, y2, visible2 = world_to_screen(tip.X, tip.Y, tip.Z)
    line.From = Vector2.new(x1, y1)
    line.To = Vector2.new(x2, y2)
    line.Visible = visible1 and visible2
end)

cf * Vector3.new(0, 0, -10) is the point 10 studs in front of the part, because the local -Z axis is the LookVector.

Constructors

CFrame.new has three forms, chosen by its arguments. The first two always produce the identity rotation, so the result faces -Z.

CFrame.new([x], [y], [z])

Creates a frame at the given position with no rotation. CFrame.new() is the identity frame at the origin.

Parameters:

  • x: number (optional, default 0) - Position X. Numeric strings are converted; any other non-nil value raises number expected.
  • y: number (optional, default 0) - Position Y.
  • z: number (optional, default 0) - Position Z.

Returns:

  • CFrame - A new frame with the identity rotation.
  • With 4 to 11 arguments, everything after z is ignored. Rotation needs all 12.

CFrame.new(position)

Creates a frame at a Vector3 position with no rotation.

Parameters:

  • position: Vector3 - The position. Any further arguments are ignored.

Returns:

  • CFrame - A new frame with the identity rotation.
lua
local spawnPoint = CFrame.new(Vector3.new(0, 5, 0))
print(spawnPoint, spawnPoint.LookVector)

CFrame.new(x, y, z, r00, r01, r02, r10, r11, r12, r20, r21, r22)

Creates a frame from a position and a 3x3 rotation matrix given row by row, in Roblox's component order.

Parameters:

  • x, y, z: number - Position. These three may be nil (treated as 0).
  • r00, r01, r02: number - First matrix row. Required numbers.
  • r10, r11, r12: number - Second matrix row. Required numbers.
  • r20, r21, r22: number - Third matrix row. Required numbers.

Returns:

  • CFrame - A new frame using the matrix exactly as given.
  • The matrix isn't validated or normalized.
  • Arguments after the twelfth are ignored.
lua
local turned = CFrame.new(0, 0, 0, 0, 0, 1, 0, 1, 0, -1, 0, 0)
print(turned.LookVector, turned.RightVector, turned.UpVector)

Properties

All properties are read-only and computed on every access. In the descriptions, r00 through r22 are the matrix entries in the order of the 12-number constructor.

CFrame.Position

The position as a new Vector3.

Read-only.

Returns:

  • Vector3

Aliases: CFrame.p

CFrame.X

The X coordinate of the position, as a Lua float.

Read-only.

Returns:

  • number

CFrame.Y

The Y coordinate of the position, as a Lua float.

Read-only.

Returns:

  • number

CFrame.Z

The Z coordinate of the position, as a Lua float.

Read-only.

Returns:

  • number

CFrame.LookVector

The forward direction (-r02, -r12, -r22). Identity gives 0, 0, -1.

Read-only.

Returns:

  • Vector3

CFrame.RightVector

The right direction, the first column: r00, r10, r20. For the identity rotation it is 1, 0, 0.

Read-only.

Returns:

  • Vector3

CFrame.UpVector

The up direction, the second column: r01, r11, r21. For the identity rotation it is 0, 1, 0.

Read-only.

Returns:

  • Vector3

Operators

ExpressionResultBehavior
cf * vVector3Transforms a point from the frame's local space to world space: the rotation times v, plus the position. CFrame.new(10, 0, 0) * Vector3.new(1, 2, 3) is 11, 2, 3.
a * bCFrameComposes two frames: b expressed in a's local space. The rotation is a's matrix times b's, and the position is a * b.Position.
cf * othererrorAny right-hand operand that is not a Vector3 or CFrame raises CFrame multiplication expects Vector3 or CFrame.
v * cf, n * cferrorThe CFrame must be on the left. A Vector3 on the left raises number expected.
a + b, a - b, -cferrorNot defined. cf + v reaches Vector3's +, which rejects the CFrame. To move a frame without changing its rotation, use CFrame.new(delta) * cf.
a == bbooleanNo __eq: true only when both sides are the same object.
tostring(cf)stringOnly the position, formatted with %g and joined by a comma and space, such as 1, 2, 3. The rotation is not printed.
typeof(cf)stringCFrame. Plain type(cf) returns userdata.

Recipes

These helpers rebuild missing Roblox functions from what does exist. They assume an orthonormal rotation, which is what parts report.

cframe-helpers.lua
local function lookAt(from, target)
    local look = (target - from).Unit
    local right = look:Cross(Vector3.yAxis).Unit
    local up = right:Cross(look)
    return CFrame.new(from.X, from.Y, from.Z, right.X, up.X, -look.X, right.Y, up.Y, -look.Y, right.Z, up.Z, -look.Z)
end

local function pointToObjectSpace(cf, point)
    local d = point - cf.Position
    return Vector3.new(d:Dot(cf.RightVector), d:Dot(cf.UpVector), -d:Dot(cf.LookVector))
end

local function translate(cf, delta)
    return CFrame.new(delta) * cf
end

local camera = lookAt(Vector3.new(0, 10, 20), Vector3.new(0, 5, 0))
print(camera.LookVector)
print(pointToObjectSpace(camera, camera * Vector3.new(1, 2, 3)))
print(translate(camera, Vector3.new(0, 10, 0)))
  • lookAt matches CFrame.lookAt(from, target) with the default up vector. It breaks down when target is straight above or below from, because the cross product with Vector3.yAxis is then zero.
  • pointToObjectSpace is the inverse of cf * v: it multiplies by the transposed rotation, which equals the inverse for an orthonormal matrix.
  • translate keeps the rotation of cf and adds delta to its position.

Storage

  • Each CFrame is a full userdata holding twelve 32-bit floats: the position X, Y, Z followed by the rotation matrix row by row (r00, r01, r02, r10, ... r22), with the metatable named clover.CFrame. That name is what appears in error messages, and typeof strips the clover. prefix.
  • Values are immutable. There is no __newindex, so cf.Position = v raises attempt to index a clover.CFrame value. Build a new frame instead.
  • Every constructor call and * allocates a new userdata, and two frames with the same contents are still different objects and different table keys.

Where CFrame is used

  • CFrame on parts (see Instance) is built from the part's position and rotation matrix; a part without physics data gives the identity frame at the origin. Assigning a CFrame writes both position and rotation; assigning a Vector3 or an X/Y/Z table writes only the position.
  • SetAttribute accepts a CFrame value (see attributes).
  • Remote calls can send CFrame arguments and receive CFrame results (see remotes).
  • TweenService cannot tween CFrame values: it raises Tween values must be finite numbers, Vector2, Vector3 or Color3 (see services).

http

Web requests and JSON.

Needs the unsafe API

Requests need Allow unsafe Lua API; otherwise they raise an error. JSONEncode, JSONDecode, GenerateGUID and UrlEncode always work.

Requests block, they do not yield

Requests block the whole VM until they finish. Don't call them every frame; fetch once and cache.

Limits

Timeouts: 10 s resolve, 10 s connect, 15 s send, 30 s per receive. Bodies are capped at 64 MiB. URLs must start with http:// or https://.

post-json.lua
local payload = HttpService:JSONEncode({player = "clover", score = 10})

local response = request({
    Url = "https://httpbin.org/post",
    Method = "POST",
    Headers = {["Content-Type"] = "application/json"},
    Body = payload,
})

if response.Success then
    local data = HttpService:JSONDecode(response.Body)
    print("server saw score", data.json.score)
else
    warn("request failed", response.StatusCode, response.StatusMessage)
end

Functions

request(options)

Sends an HTTP request and returns a response table. Failed requests don't raise; check Success and StatusCode.

Requires the unsafe API.

Parameters:

Returns:

  • response: table - Always returned, also on failure. See Response table.

Aliases: http_request, HttpService:RequestAsync

  • HttpService:RequestAsync(options) calls the same code and returns the same table, so it does not raise for HTTP or network failures either.
  • Method is sent exactly as written, so use uppercase ("POST", not "post").
  • Body must already be a string. Tables are not encoded for you, so run them through HttpService:JSONEncode first.
lua
local response = http_request({
    Url = "https://api.github.com/repos/lua/lua",
    Headers = {Accept = "application/json"},
})
print(response.StatusCode, #response.Body)

Request options

KeyTypeDescription
UrlstringRequired. Absolute http:// or https:// URL. URL is read when Url is nil. A URL WinHTTP cannot parse gives StatusMessage = "invalid url".
MethodstringOptional, default "GET". Any verb ("POST", "PUT", "PATCH", "DELETE", ...) is passed through verbatim.
HeaderstableOptional. Request headers as name = value. Only pairs where both key and value are strings are sent, others are skipped silently.
BodystringOptional. Raw request body, binary safe. Non-string values (other than numbers) are ignored and no body is sent.

Any other key (for example Cookies) is ignored.

Response table

FieldTypeDescription
Successbooleantrue only when StatusCode is 200 to 299.
StatusCodeintegerHTTP status code. 0 when no response arrived (bad URL, DNS or connection failure, timeout) or the body exceeded 64 MiB.
StatusMessagestring"OK" on success and "HTTP error" for any other HTTP status (not the server's reason phrase). On transport failure it is one of "invalid url", "WinHttpOpen failed", "WinHttpConnect failed", "WinHttpOpenRequest failed", "request failed" or "response too large".
BodystringRaw response body, binary safe. Filled for error statuses too (a 404 page, an API error JSON). Empty string on transport failure.
HeaderstableResponse headers as name = value. Names keep the server's casing. Values keep the space after the colon (usually " application/json"), and a repeated header such as Set-Cookie keeps only its last value.
read-header.lua
local response = request({Url = "https://example.com"})
local contentType = response.Headers["Content-Type"] or response.Headers["content-type"]
if contentType then
    print(contentType:match("^%s*(.-)%s*$"))
end

HttpGet(url)

Performs a GET request and returns the response body. Unlike request, failures raise an error.

Requires the unsafe API.

Parameters:

  • url: string - Absolute http:// or https:// URL.

Returns:

  • body: string - Response body when the status is 200 to 299.

Aliases: game:HttpGet, game:HttpGetAsync, HttpService:GetAsync

  • Errors: HttpGet failed: HTTP <code> for a non-2xx status, or HttpGet failed: <reason> when no response arrived (reason is a StatusMessage value from the response table).
  • Extra arguments are ignored, so game:HttpGet(url, true) works. Use request for headers.
  • game:HttpGet and game:HttpGetAsync only exist while game is set (you are in a game). The global HttpGet always works.
lua
local ok, source = pcall(HttpGet, "https://raw.githubusercontent.com/user/repo/main/script.lua")
if not ok then
    warn(source)
    return
end
local chunk, problem = loadstring(source, "=remote")
if chunk then
    chunk()
else
    warn(problem)
end

HttpService

HttpService is a plain global table, also returned by game:GetService("HttpService"). Call its functions with a colon. HttpService:GetAsync is HttpGet and HttpService:RequestAsync is request.

HttpService:PostAsync(url, data, [contentType])

Sends a POST request with data as the body and a Content-Type header, and returns the response body. Implemented in Lua on top of request.

Requires the unsafe API.

Parameters:

  • url: string - Absolute http:// or https:// URL.
  • data: string - Request body. Encode tables with JSONEncode first.
  • contentType: string (optional, default "application/json") - Value of the Content-Type header.

Returns:

  • body: string - Response body when Success is true.
  • Raises HTTP <StatusCode> (prefixed with the caller's position) when the request does not succeed. Transport failures report HTTP 0.
lua
local body = HttpService:JSONEncode({content = "hello"})
local ok, result = pcall(HttpService.PostAsync, HttpService, "https://httpbin.org/post", body)
if ok then
    print(result)
else
    warn(result)
end

HttpService:JSONEncode(value)

Serializes a Lua value to a compact JSON string (no spaces or newlines). See JSON type mapping for how each type is written.

Parameters:

  • value: any - nil, boolean, number, string or a table of those.

Returns:

  • json: string - The encoded JSON text.
  • Raises an error for functions, userdata, threads, cycles, or nesting deeper than 64.
  • Object key order is unspecified.
lua
print(HttpService:JSONEncode({1, 2, 3}))
print(HttpService:JSONEncode({name = "clover", alive = true}))
print(HttpService:JSONEncode({}))

HttpService:JSONDecode(json)

Parses JSON text into Lua values. Objects become tables with string keys, arrays become sequences starting at 1.

Parameters:

  • json: string - JSON text. Leading and trailing whitespace is allowed, any other trailing text is an error.

Returns:

  • value: any - The decoded value. JSON null at the top level returns nil.
  • Every JSON number decodes to a float: 3 becomes 3.0. Use math.tointeger(x) when you need an integer.
  • Raises JSONDecode: invalid JSON on any syntax error, trailing text or nesting deeper than 64 levels.
lua
local data = HttpService:JSONDecode('{"name":"clover","tags":["a","b"],"count":3,"extra":null}')
print(data.name, data.tags[2], data.count, math.type(data.count), data.extra)
print(math.tointeger(data.count))

HttpService:GenerateGUID([wrap])

Returns a random version-4 style GUID in lowercase hex, such as {3f2b9c1e-7a4d-4e0b-9c51-0d8e2a6f1b73}.

Parameters:

  • wrap: boolean (optional, default true) - Pass false to omit the curly braces.

Returns:

  • guid: string - 36 characters, or 38 with braces.
lua
print(HttpService:GenerateGUID())
print(HttpService:GenerateGUID(false))

HttpService:UrlEncode(text)

Percent-encodes text for use in a URL. Letters, digits and - _ . ~ are kept, every other byte becomes %XX with uppercase hex (a space becomes %20).

Parameters:

  • text: string - Text to encode. Other values are passed through tostring first.

Returns:

  • encoded: string - The encoded text.
lua
local query = HttpService:UrlEncode("clover docs & more")
print("https://example.com/search?q=" .. query)

HttpService.HttpEnabled

Always true. Exists for scripts that check it before making requests. Whether requests actually work depends on the unsafe API toggle.

JSON type mapping

Encoding (Lua to JSON)

Lua valueJSON output
nilnull
booleantrue or false
numberFormatted with %.17g: 5 gives 5, 0.1 gives 0.10000000000000001, 1e20 gives 1e+20. Integers above 2^53 lose precision. NaN and infinity produce text that is not valid JSON.
stringQuoted. ", \, \b, \f, \n, \r, \t are escaped, other control bytes become \u00XX. Bytes 0x80 and up (UTF-8) and / are copied unchanged.
table with #t > 0Array of t[1] to t[#t]. Holes inside that range become null. All other keys are dropped.
table with #t == 0Object of its string keys. Non-string keys are dropped. An empty table gives {}. If the first key next returns is a positive integer, the whole table is written as [] and every key is lost: {[5] = "x"} always gives [], and a table that mixes string keys with positive integer keys but has no t[1] can come out as [] too.
function, userdata, threadError JSONEncode: unsupported value

Decoding (JSON to Lua)

JSON valueLua value
objectTable with string keys. A repeated key keeps its last value.
arraySequence table starting at index 1.
stringString. \uXXXX escapes become UTF-8. Surrogate pairs are not combined, so escaped emoji come out as invalid UTF-8.
numberFloat, always (1 becomes 1.0). Parsed with C strtod, so it is a little more lenient than strict JSON.
true / falseboolean
nullnil. Object keys holding null are absent. Array entries become holes, so # can stop early: [1,null,3] decodes to a table where #t can be 1.

WebSocket

Connect to ws:// and wss:// servers.

Needs the unsafe API

Needs Allow unsafe Lua API. A VM reset closes every socket without firing OnClose.

Blocking and threading

connect and Send block until done. Incoming messages are delivered on the script thread, up to 64 per pass.

LimitValue
Open sockets at once8
URL length2048 bytes
Outgoing message8 MiB (8388608 bytes)
Incoming message16 MiB (16777216 bytes), larger closes the socket with code 1009
Undelivered backlog, shared by all sockets4096 events or 32 MiB of message data, more closes the socket with code 1008
Close reason123 bytes
Handshake timeouts5 s resolve, 5 s connect, 8 s send, 8 s receive
echo-socket.lua
local socket = WebSocket.connect("wss://echo.websocket.org")

socket.OnMessage:Connect(function(message, isBinary)
    if isBinary then
        print("binary message,", #message, "bytes")
    else
        print("text message:", message)
    end
end)

socket.OnClose:Connect(function(code, reason)
    print("socket closed", code, reason)
end)

socket:Send(HttpService:JSONEncode({type = "hello", from = "clover"}))
socket:Send(crypt.random(16), true)

task.delay(10, function()
    if socket:IsOpen() then
        socket:Close()
    end
end)

Functions

WebSocket.connect(url)

Opens a WebSocket connection and returns a socket object once the handshake has completed. Raises WebSocket connect failed: <reason> when it cannot connect.

Requires the unsafe API.

Parameters:

  • url: string - A ws:// or wss:// URL. See URL rules.

Returns:

  • socket: table - A new table with Send, Close, IsOpen, OnMessage and OnClose.
  • Only the lowercase name connect exists. WebSocket:connect(url) also works.
  • The handshake sends no extra headers: custom headers, cookies and subprotocols are not supported.
  • wss:// uses TLS and the server certificate must verify.
  • Common reasons: too many open WebSockets (the limit is 8), close one first, the url must start with ws:// or wss://, the server refused the WebSocket upgrade (HTTP <status>), the host name could not be resolved (...), could not connect to the server (...), the request timed out (...), the TLS certificate could not be verified (...).
lua
local ok, socket = pcall(WebSocket.connect, "ws://127.0.0.1:8080/feed?room=1")
if not ok then
    warn(socket)
    return
end
print("connected", socket:IsOpen())

URL rules

PartRule
Schemews:// or wss://, any letter case. Anything else fails with the url must start with ws:// or wss://.
HostLetters, digits, ., - and _, or an IPv6 address in brackets such as ws://[::1]:8080.
PortOptional, 1 to 65535. Defaults to 80 for ws:// and 443 for wss://.
Path and querySent as written, / when empty. A #fragment is dropped.
Not alloweduser:pass@ credentials, spaces, control characters, non-ASCII bytes (percent-encode them) and URLs over 2048 bytes.

Methods

socket:Send(message, [binary])

Sends one message. Text messages must be valid UTF-8. Raises WebSocket send failed: <reason> on failure.

Parameters:

  • message: string - The payload, up to 8 MiB. Numbers are converted to strings.
  • binary: boolean (optional, default false) - Any truthy value sends a binary frame instead of a text frame.
  • Reasons: the socket is closed, the message is too large (the limit is 8 MB), text messages must be valid UTF-8, pass true as the second argument to send binary data, or a WinHTTP send error.
  • Raw bytes such as crypt.random(16) are usually not valid UTF-8, so send them with binary set to true.
lua
local socket = WebSocket.connect("wss://echo.websocket.org")
socket:Send("hello")
socket:Send(string.pack("<I4f", 7, 1.5), true)

socket:Close()

Closes the connection. OnClose fires on a later pass.

  • Calling it again, or after OnClose has fired, does nothing.
lua
local socket = WebSocket.connect("wss://echo.websocket.org")
socket:Close()
print(socket:IsOpen())

socket:IsOpen()

Reports whether the socket is connected and usable.

Returns:

  • open: boolean - false once you call Close, the server closes, or the connection drops. It can turn false before OnClose has fired.
lua
local socket = WebSocket.connect("wss://echo.websocket.org")
if socket:IsOpen() then
    socket:Send("still here")
end

Events

OnMessage and OnClose are signals. A handler that errors is disconnected.

socket.OnMessage

Fires for every complete message received. Fragmented messages are reassembled first, so a handler always gets the whole message.

Parameters:

  • message: string - The message bytes.
  • isBinary: boolean - true for a binary message, false for text.
lua
local socket = WebSocket.connect("wss://echo.websocket.org")
socket.OnMessage:Connect(function(message, isBinary)
    if isBinary then
        return
    end
    local ok, data = pcall(HttpService.JSONDecode, HttpService, message)
    if ok and type(data) == "table" then
        print("event", data.type)
    end
end)

socket.OnClose

Fires once when the connection ends. Afterwards the socket is released: IsOpen returns false and Send raises the socket is closed.

Parameters:

  • code: integer - Close code. See Close codes.
  • reason: string - Close reason, at most 123 bytes. Often empty when the server sends none.
lua
local socket = WebSocket.connect("wss://echo.websocket.org")
socket.OnClose:Connect(function(code, reason)
    if code ~= 1000 then
        warn("socket dropped", code, reason)
    end
end)

Close codes

CodeReasonWhen
1000closedYou called socket:Close().
1005from the serverThe server closed without sending a status code.
1006connection lostReading from the connection failed (network drop, server vanished).
1008message queue fullMore than 4096 events or 32 MiB of message data were waiting to be delivered.
1009message too largeAn incoming message exceeded 16 MiB.
otherfrom the serverThe code and reason the server sent in its close frame.

crypt

Hashing, secure random bytes and base64.

Byte strings in, byte strings out

No unsafe API needed. crypt.hash returns lowercase hex. crypt.random and base64decode return raw bytes.

No encryption

crypt only has the functions on this page. There is no encrypt, decrypt, HMAC or signature function.

crypt-basics.lua
local function toHex(bytes)
    return (bytes:gsub(".", function(c) return string.format("%02x", c:byte()) end))
end

print("random bytes", toHex(crypt.random(16)))
print("sha256", crypt.hash("hello clover"))
print("md5", crypt.hash("hello clover", "md5"))

local encoded = base64encode("hello clover")
print(encoded, base64decode(encoded) == "hello clover")
print("session key", crypt.generatekey())

Functions

crypt.hash(data, [algorithm])

Hashes data with Windows CNG and returns the digest as lowercase hex.

Parameters:

  • data: string - Bytes to hash, up to 256 MiB. Numbers are converted to strings.
  • algorithm: string (optional, default "sha256") - "md5", "sha1", "sha256", "sha384" or "sha512", any letter case. Spellings like "sha-256" are rejected.

Returns:

  • digest: string - Hex digest: 32 characters for md5, 40 for sha1, 64 for sha256, 96 for sha384, 128 for sha512.
  • Raises crypt.hash: unsupported algorithm (md5, sha1, sha256, sha384, sha512) or input too large.
lua
print(crypt.hash(""))
print(crypt.hash("abc", "SHA1"))
print(#crypt.hash("abc", "sha512"))

crypt.random([count])

Returns count bytes from the system cryptographic random generator (BCryptGenRandom).

Parameters:

  • count: integer (optional, default 16) - Number of bytes, 0 to 1048576 (1 MiB). 0 returns an empty string.

Returns:

  • bytes: string - Raw random bytes of length count.
  • Raises crypt.random: count must be between 0 and 1048576 for a count outside that range. A non-integral number such as 1.5 raises a standard argument error.
lua
local nonce = crypt.random(12)
print(#nonce, base64encode(nonce))

crypt.base64encode(data)

Encodes bytes as standard base64 (alphabet A-Z a-z 0-9 + /) with = padding and no line breaks.

Parameters:

  • data: string - Bytes to encode. Numbers are converted to strings.

Returns:

  • encoded: string - Base64 text, 4 * math.ceil(#data / 3) characters long.

Aliases: base64encode, base64_encode, crypt.base64.encode, base64.encode, clover.base64encode

lua
print(base64encode("clover"))
print(crypt.base64.encode("\0\1\2"))

crypt.base64decode(data)

Decodes base64 to bytes. Never raises; invalid characters are skipped.

Parameters:

  • data: string - Base64 text.

Returns:

  • decoded: string - The decoded bytes. Garbage input gives garbage or an empty string, not an error.

Aliases: base64decode, base64_decode, crypt.base64.decode, base64.decode, clover.base64decode

  • URL-safe base64 (- and _) is not understood because those characters are skipped. Translate them to + and / first, as in the example.
lua
print(base64decode("YnVubmk="))
local urlSafe = "-_8="
local standard = urlSafe:gsub("%-", "+"):gsub("_", "/")
print(#base64decode(standard))

crypt.generatebytes([count])

Returns count cryptographically secure random bytes encoded as base64. Same as base64encode(crypt.random(count)).

Parameters:

  • count: integer (optional, default 16) - Number of random bytes before encoding, 0 to 1048576. Errors from crypt.random propagate.

Returns:

  • encoded: string - Base64 text (24 characters for the default 16 bytes).
lua
print(crypt.generatebytes())
print(crypt.generatebytes(48))

crypt.generatekey()

Returns 32 cryptographically secure random bytes encoded as base64, a 44-character string. Same as base64encode(crypt.random(32)).

Returns:

  • key: string - Base64 text of 32 random bytes.
lua
local key = crypt.generatekey()
print(key, #base64decode(key))

Fields

crypt.base64

Table with encode and decode, the same as base64encode and base64decode.

Aliases: base64

lua
local encoded = crypt.base64.encode("clover")
print(base64.decode(encoded))
print(crypt.base64.encode == base64encode)

bit32

The bit32 library: bitwise operations on 32-bit unsigned integers, in the style of Lua 5.2 and Luau.

How numbers are converted

Arguments are truncated toward zero and reduced to 32 bits, so -1 becomes 4294967295. Results are integers from 0 to 4294967295.

Shift counts wrap at 32

Shift counts are masked with & 31: bit32.lshift(1, 32) returns 1, and a negative count does not reverse direction. Use rshift for right shifts.

Lua 5.4's own bitwise operators (&, |, ~, <<, >>) work on 64-bit integers and are available too. Use bit32 when you want 32-bit wraparound. See Standard Library.

pack-color.lua
local function packColor(r, g, b)
    return bit32.bor(bit32.lshift(r, 16), bit32.lshift(g, 8), b)
end

local packed = packColor(255, 128, 64)
print(string.format("0x%06X", packed))
print(bit32.extract(packed, 16, 8), bit32.extract(packed, 8, 8), bit32.extract(packed, 0, 8))

local flags = 0
flags = bit32.replace(flags, 1, 3)
print(flags, bit32.band(flags, 8) ~= 0)

Only the eleven functions below exist. btest, countlz, countrz and byteswap are not provided: write bit32.band(a, b) ~= 0 instead of btest.

Functions

bit32.band(...)

Bitwise AND of all arguments.

Parameters:

  • ...: number - Zero or more values.

Returns:

  • result: integer - The AND of every argument, or 4294967295 when called with no arguments.
lua
print(bit32.band(0xF0F0, 0xFF00))
print(bit32.band(-1, 0xFF))

bit32.bor(...)

Bitwise OR of all arguments.

Parameters:

  • ...: number - Zero or more values.

Returns:

  • result: integer - The OR of every argument, or 0 when called with no arguments.
lua
print(bit32.bor(1, 2, 8))

bit32.bxor(...)

Bitwise exclusive OR of all arguments.

Parameters:

  • ...: number - Zero or more values.

Returns:

  • result: integer - The XOR of every argument, or 0 when called with no arguments.
lua
print(bit32.bxor(0xFF, 0x0F))

bit32.bnot(x)

Flips all 32 bits of x.

Parameters:

  • x: number - Value to invert.

Returns:

  • result: integer - 4294967295 - x after conversion.
lua
print(bit32.bnot(0))
print(bit32.bnot(0xFFFF0000))

bit32.lshift(x, disp)

Logical left shift. Bits shifted past bit 31 are discarded and zeros fill in from the right.

Parameters:

  • x: number - Value to shift.
  • disp: integer - Shift count, masked with & 31. Must have an exact integer value.

Returns:

  • result: integer - The shifted 32-bit value.
lua
print(bit32.lshift(1, 31))
print(bit32.lshift(0xFF, 28))
print(bit32.lshift(1, 32))

bit32.rshift(x, disp)

Logical right shift. Zeros fill in from the left.

Parameters:

  • x: number - Value to shift.
  • disp: integer - Shift count, masked with & 31.

Returns:

  • result: integer - The shifted 32-bit value.
lua
print(bit32.rshift(0x80000000, 31))
print(bit32.rshift(-1, 28))

bit32.arshift(x, disp)

Arithmetic right shift. x is treated as a signed 32-bit value, so bit 31 is copied into the vacated bits. The result is returned unsigned.

Parameters:

  • x: number - Value to shift.
  • disp: integer - Shift count, masked with & 31.

Returns:

  • result: integer - The shifted value as an unsigned 32-bit integer.
lua
print(bit32.arshift(0x80000000, 4))
print(bit32.arshift(-16, 2))
print(bit32.arshift(64, 2))

bit32.lrotate(x, disp)

Rotates the 32 bits of x left. Bits leaving at the top come back in at the bottom.

Parameters:

  • x: number - Value to rotate.
  • disp: integer - Rotation count, masked with & 31. -1 rotates by 31, which equals rotating right by 1.

Returns:

  • result: integer - The rotated value.
lua
print(bit32.lrotate(0x80000001, 1))
print(bit32.lrotate(1, -1))

bit32.rrotate(x, disp)

Rotates the 32 bits of x right. Bits leaving at the bottom come back in at the top.

Parameters:

  • x: number - Value to rotate.
  • disp: integer - Rotation count, masked with & 31.

Returns:

  • result: integer - The rotated value.
lua
print(bit32.rrotate(1, 1))
print(string.format("%08X", bit32.rrotate(0x12345678, 8)))

bit32.extract(n, field, [width])

Returns the unsigned number made of bits field to field + width - 1 of n. Bit 0 is the least significant.

Parameters:

  • n: number - Source value.
  • field: integer - Index of the lowest bit to read, 0 to 31.
  • width: integer (optional, default 1) - Number of bits to read, 1 to 32.

Returns:

  • result: integer - The extracted bits, shifted down to bit 0.
  • Raises field out of range when field < 0, width <= 0 or field + width > 32.
lua
print(bit32.extract(0xABCD, 4, 8))
print(bit32.extract(5, 2))

bit32.replace(n, v, field, [width])

Returns n with bits field to field + width - 1 replaced by the low width bits of v.

Parameters:

  • n: number - Original value.
  • v: number - New bits. Only its low width bits are used.
  • field: integer - Index of the lowest bit to write, 0 to 31.
  • width: integer (optional, default 1) - Number of bits to write, 1 to 32.

Returns:

  • result: integer - The updated value.
  • Raises field out of range when field < 0, width <= 0 or field + width > 32.
lua
print(bit32.replace(0, 0xF, 4, 4))
print(bit32.replace(0xFF, 0, 0, 4))

memory

Raw reads and writes of the attached Roblox process (RobloxPlayerBeta.exe) at absolute addresses.

No safety net

Always available, even without the unsafe API. clover.memory_write writes into live Roblox; a wrong write can crash it. Memory layouts change with Roblox updates.

Failures are silent

Bad addresses never raise: they read as 0, false or "", and failed writes are ignored.

clover table only

Call them through clover, for example clover.read_int(...). Each call is a synchronous cross-process read.

pe-header.lua
local base = clover.getbase()
if base == 0 then
    warn("clover is not attached to Roblox")
    return
end

local magic = clover.memory_read("short", base)
local peHeader = clover.read_int(base + 0x3C)
local signature = clover.memory_read("uint", base + peHeader)
local sections = clover.memory_read("short", base + peHeader + 6)

print(string.format("base 0x%X", base))
print("MZ header:", magic == 0x5A4D, "PE signature:", signature == 0x4550)
print("sections:", sections)

Address arguments

Addresses are absolute 64-bit integers like 0x7FF6A1230000. They aren't checked before reading.

Places to get addresses from:

  • clover.getbase(): the base of the RobloxPlayerBeta.exe module. Add module-relative addresses to it.
  • Instance.Address on any Instance: the address of the Roblox object behind it. See Instance.
  • The address and humanoid fields of the entries returned by clover.players() and clover.local_player(). See players.
  • Pointers you read yourself with clover.read_pointer.

Type names

clover.memory_read and clover.memory_write take one of these exact, lowercase type names as their first argument. Any other name raises memory_read: unknown type '<name>' (or memory_write: ...).

TypeSizememory_read returnsmemory_write expects
"byte"1 byte, unsignedinteger 0 to 255integer; only the low 8 bits are stored
"bool"1 bytetrue if the byte is not 0any value; truthy writes 1, nil or false writes 0
"short"2 bytes, signedinteger -32768 to 32767integer; only the low 16 bits are stored
"int"4 bytes, signedintegerinteger; only the low 32 bits are stored
"uint"4 bytes, unsignedinteger 0 to 4294967295integer; only the low 32 bits are stored
"int64"8 bytes, signedintegerinteger
"uintptr_t"8 bytesinteger (pointer value)integer
"float"4 bytesnumber (single precision)number
"double"8 bytesnumbernumber
"string"variablestring in the std::string layout, up to 255 bytes; see clover.read_stringstring; its raw bytes are copied to address, nothing else

Integer types accept floats with an exact integer value (5.0), but a fractional value such as 5.5 raises number has no integer representation.

Functions

clover.memory_read(type, address)

Reads one value of the given type at address and converts it to a Lua value.

Parameters:

  • type: string - One of the type names.
  • address: integer - Absolute address to read.

Returns:

  • value: integer | number | boolean | string - The value read. When the address is 0 or cannot be read you get the zero value of the type: 0, 0.0, false or "".
lua
local base = clover.getbase()
local peHeader = clover.memory_read("int", base + 0x3C)
local sections = clover.memory_read("short", base + peHeader + 6)
print("PE header at +" .. peHeader .. ", sections: " .. sections)

clover.memory_write(type, address, value)

Writes value to address as the given type. Returns nothing and gives no indication of whether the write succeeded.

Parameters:

  • type: string - One of the type names.
  • address: integer - Absolute address to write.
  • value: any - An integer, number, any value for "bool", or a string, matching type.
  • "string" copies raw bytes only, with no terminator or length update.
  • Not gated by the unsafe API toggle.
lua
local function patchFloat(address, value)
    local before = clover.memory_read("float", address)
    clover.memory_write("float", address, value)
    return before, clover.memory_read("float", address)
end

clover.read_int(address)

Reads a signed 32-bit integer. Same as clover.memory_read("int", address).

Parameters:

  • address: integer - Absolute address to read.

Returns:

  • integer - The value, or 0 if the read failed.
lua
local base = clover.getbase()
print("e_lfanew:", clover.read_int(base + 0x3C))

clover.read_int64(address)

Reads a signed 64-bit integer. Same as clover.memory_read("int64", address).

Parameters:

  • address: integer - Absolute address to read.

Returns:

  • integer - The value, or 0 if the read failed.

clover.read_float(address)

Reads a 32-bit float. Same as clover.memory_read("float", address).

Parameters:

  • address: integer - Absolute address to read.

Returns:

  • number - The value, or 0.0 if the read failed.

clover.read_pointer(address)

Reads an 8-byte pointer. Same as clover.memory_read("uintptr_t", address).

Parameters:

  • address: integer - Absolute address to read.

Returns:

  • integer - The pointer value, or 0 if the read failed.
lua
local function follow(address, steps)
    for _, step in ipairs(steps) do
        address = clover.read_pointer(address + step)
        if address == 0 then return nil end
    end
    return address
end

clover.read_string(address)

Reads an MSVC std::string at address.

Parameters:

  • address: integer - Address of the std::string object (not of its characters).

Returns:

  • string - The bytes read. Empty when address is outside 0x10000 to 0x7FFFFFFEFFFF, when the length is 0, negative or over 255, or when the data pointer is outside that range.
  • Strings longer than 255 bytes cannot be read this way and come back empty.
  • The bytes are returned as-is and may contain zero bytes. If the length is readable but the characters are not, the result is that many zero bytes.
  • Same as clover.memory_read("string", address).
lua
local function readName(stringAddress)
    local text = clover.read_string(stringAddress)
    if text == "" then return nil end
    return text
end

clover.getbase()

Returns the base address of the RobloxPlayerBeta.exe module in the attached process.

Returns:

  • integer - The module base, or 0 when no Roblox client is attached.

Aliases: clover.module_base

lua
local base = clover.getbase()
if base ~= 0 then
    print(string.format("RobloxPlayerBeta.exe at 0x%X", base))
end

Remotes

Fire remotes, invoke RemoteFunctions and trigger signals and ProximityPrompts.

Locked to one Roblox build

Only works on the supported Roblox build; otherwise raises <name>: unsupported Roblox build. No unsafe API needed.

Calls block the whole VM

Each call runs inside Roblox and blocks the VM, from about a second up to 10 s or more for invokeserver. Never call them every frame.

Errors are raised

Failures raise an error starting with the function name. Use pcall (see Errors).

buy-item.lua
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local buy = ReplicatedStorage:WaitForChild("BuyItem", 5)
local price = ReplicatedStorage:FindFirstChild("GetPrice")

if price then
    local ok, cost = pcall(invokeserver, price, "Sword")
    if ok then
        print("Sword costs", cost)
    else
        warn(cost)
    end
end

if buy then
    local ok, err = pcall(function()
        buy:FireServer("Sword", 1)
    end)
    if not ok then
        warn("FireServer failed:", err)
    end
end

Argument types

fireserver and invokeserver accept at most 16 arguments after the remote. Each one is converted to a Roblox value by its Lua type:

Lua valueSent asNotes
booleanbool
numberdoubleIntegers and floats are both sent as a 64-bit float.
stringstringStrings up to 15 bytes are stored inline. Longer strings share a 4 KB argument block with the other arguments: one long string can be at most 3935 bytes, and the space shrinks as more arguments are passed.
Vector2Vector232-bit float components.
Vector3Vector332-bit float components.
Color3Color332-bit float components.
CFrameCFrameRotation matrix and position as 32-bit floats.
InstanceInstance referenceSent as a shared reference to the instance. An instance that is not held by a live shared reference in Roblox raises instance is not shared-owned.
anything elserejectednil, tables, functions, threads and other userdata raise unsupported argument type. A table that looks like a Vector3 is not accepted; pass a real Vector3.

If Roblox has not used one of the argument types yet in this session, the call fails with argument type not ready in this session. For Color3 and Instance arguments the engine first tries to initialise the type with an extra engine call.

Values returned by invokeserver

invokeserver returns every value in the server's reply, in order, as separate return values. A reply with more than 32 values, or a string longer than 64 KB, raises InvokeServer: could not read the reply.

Reply valueLua value
boolboolean
numbernumber, always a float: 5 comes back as 5.0. Use math.tointeger when you need an integer.
stringstring
Vector2, Vector3, Color3, CFramethe matching data type
Instancean Instance, or nil when the reference is empty
any other type (tables, nil, enums, ...)nil in that position

Functions

fireserver(remote, ...)

Fires a RemoteEvent to the server. Doesn't wait for the server. See Argument types.

Parameters:

  • remote: Instance - A RemoteEvent. Other classes, including UnreliableRemoteEvent, raise an error.
  • ...: any (optional) - Up to 16 values of the supported types.

Aliases: Instance:FireServer

  • remote:FireServer(...) is the same function. Non-RemoteEvents raise an error.
  • FireServer: engine call did not run means no Roblox job picked up the call within about 1 s.
lua
local remote = game:GetService("ReplicatedStorage"):FindFirstChild("Chat")
if remote then
    fireserver(remote, "hello", 42, Vector3.new(0, 10, 0), true)
end

invokeserver(remote, ...)

Invokes a RemoteFunction and waits up to about 10 s for the reply, then returns it.

Parameters:

  • remote: Instance - A RemoteFunction. Any other class raises InvokeServer: not a RemoteFunction.
  • ...: any (optional) - Up to 16 values of the supported types.

Returns:

Aliases: Instance:InvokeServer

  • Raises an error after 10 seconds without a reply.
  • One call at a time. If 64 calls are still waiting for late replies, new calls raise an error.
  • When Roblox reports an error for the invocation, it raises InvokeServer: server error: followed by Roblox's error text, truncated to 223 bytes.
  • Argument errors use the InvokeServer: prefix, for example InvokeServer: unsupported argument type.
lua
local stats = game:GetService("ReplicatedStorage"):FindFirstChild("GetStats")
if stats then
    local ok, coins, level = pcall(invokeserver, stats, "coins")
    if ok then
        print("coins", math.tointeger(coins) or coins, "level", level)
    else
        warn(coins)
    end
end

firesignal(instance, signal, [argument])

Calls the game's own handlers for a supported signal on instance. See the supported signals.

Parameters:

  • instance: Instance - The Humanoid, ClickDetector or ProximityPrompt that owns the signal.
  • signal: string - Signal name, case-sensitive: Jumping, Died, MouseClick, RightMouseClick, MouseHoverEnter, MouseHoverLeave or Triggered.
  • argument: Player | boolean (optional) - The Player for ClickDetector and ProximityPrompt signals, or a boolean for Humanoid.Jumping (default true).
  • Raises an error if a connection uses a callback type the engine doesn't support.
  • Raises an error when the signal has no connections.
  • The argument must be a Player Instance for ClickDetector and ProximityPrompt signals.
  • This fires the game's handlers inside Roblox. It does not fire connections your script made on the clover signal objects.
lua
local player = game:GetService("Players").LocalPlayer
local button = workspace:FindFirstChild("Button")
local detector = button and button:FindFirstChildOfClass("ClickDetector")
if detector then
    firesignal(detector, "MouseClick", player)
end

Signals firesignal can fire

ClassSignalThird argument
HumanoidJumpingboolean, defaults to true
HumanoidDiednot needed
ClickDetectorMouseClickthe Player (required)
ClickDetectorRightMouseClickthe Player (required)
ClickDetectorMouseHoverEnterthe Player (required)
ClickDetectorMouseHoverLeavethe Player (required)
ProximityPromptTriggeredthe Player (required)

Any other class and signal pair raises firesignal: signal not supported. If the signal's location in memory does not match the current build it raises firesignal: signal descriptor does not match this build.

fireproximityprompt(prompt)

Holds and releases a ProximityPrompt as if the player used it.

Parameters:

  • prompt: Instance - A ProximityPrompt. Anything else raises fireproximityprompt: argument must be a ProximityPrompt.
  • HoldDuration is read from the prompt. A value of 30 s or more, or 0 or less, is treated as 0, so the wait is then only 200 ms.
  • Extra arguments that some executors accept (an amount or a skip-hold flag) are ignored.
  • It does not move your character or change the prompt's properties.
  • It does not fire your script's own prompt.Triggered connections; use prompt:Fire for those.
  • Raises fireproximityprompt: InputHoldBegin did not run or fireproximityprompt: InputHoldEnd did not run when the matching engine call failed.
lua
local door = workspace:FindFirstChild("Door")
local prompt = door and door:FindFirstChildOfClass("ProximityPrompt")
if prompt then
    fireproximityprompt(prompt)
end

Methods

Instance:Fire(...)

Fires your own connections on a BindableEvent's Event or a ProximityPrompt's Triggered. Nothing is sent to Roblox.

Parameters:

  • ...: any (optional) - Any Lua values, passed unchanged to the handlers.
  • Any other class raises Fire: unsupported instance.
  • Handlers run before Fire returns. A handler that raises an error is logged and disconnected.
  • See fireserver and invokeserver for the other Instance methods.
lua
local bindable = game:GetService("ReplicatedStorage"):FindFirstChildOfClass("BindableEvent")
if bindable then
    bindable.Event:Connect(function(message, count)
        print("got", message, count)
    end)
    bindable:Fire("hello", 3)
end

OnClientEvent is local to the VM

OnClientEvent, Event and Triggered only fire when a script calls :Fire(...), never from real game traffic.

Errors

MessageRaised byMeaning
unsupported Roblox buildall fourThe running Roblox build isn't supported yet.
not a RemoteEvent / not a RemoteFunctionfireserver / invokeserverThe first argument has the wrong class.
too many argumentsfireserver, invokeserverMore than 16 arguments after the remote.
unsupported argument typefireserver, invokeserverAn argument is not a boolean, number, string, Vector2, Vector3, Color3, CFrame or Instance.
bad CFrame argument (or Vector3, Color3, Vector2)fireserver, invokeserverA value of that type could not be read.
instance is not shared-ownedfireserver, invokeserverThe engine cannot take a reference on an Instance argument.
argument type not ready in this sessionfireserver, invokeserverRoblox has not initialised that value type yet.
allocation failedfireserver, invokeserver, firesignalMemory could not be allocated in the Roblox process.
could not write argument blockfireserver, invokeserverThe long strings do not fit in the 4 KB argument block, or the block could not be written.
could not install the reply handlersinvokeserverThe first-use setup of the reply handlers failed.
could not write the callbacksinvokeserverThe reply callbacks could not be written to Roblox memory.
too many calls are still waiting for a replyinvokeserverAll 64 reply slots are held by unanswered calls.
no reply from the server within 10 secondsinvokeserverTimeout.
server error: ...invokeserverRoblox reported an error for the invocation.
could not read the replyinvokeserverThe reply had more than 32 values, a string over 64 KB, or could not be read.
engine call did not runfireserver, invokeserver, firesignalNo Roblox job picked up the call within about 1 s, or the call could not be set up.
first argument must be an InstancefiresignalThe first argument is not an Instance.
signal not supportedfiresignalThe class and signal pair is not in the supported list, or Humanoid.Jumping has no connections.
signal descriptor does not match this buildfiresignalThe signal could not be located on this build.
signal has no connectionsfiresignalNothing is connected to the signal.
signal has no supported connectionsfiresignalA connection uses a callback the engine cannot call.
third argument must be the Player / third argument must be a PlayerfiresignalThe Player argument is missing, not an Instance, or not a usable Player.
argument must be a ProximityPromptfireproximitypromptThe argument is not a ProximityPrompt.
InputHoldBegin did not run / InputHoldEnd did not runfireproximitypromptThe engine call failed.
Fire: unsupported instanceInstance:FireThe instance is not a BindableEvent or ProximityPrompt.

Messages from fireserver and Instance:FireServer start with FireServer: , from invokeserver and Instance:InvokeServer with InvokeServer: , and from the other two with firesignal: and fireproximityprompt: .

Scripts

List game scripts, read their bytecode and decompile it.

Read from client memory

These read Roblox memory and never run code in the game, so getsenv always returns an empty table. No unsafe API needed.

decompile always returns a string

They don't raise on bad input: getscriptbytecode returns "" and decompile may return a disassembly or failure report. Check the result.

Synchronous

Decompiling blocks the VM until done. Don't call it every frame.

dump-scripts.lua
local me = game:GetService("Players").LocalPlayer
if not me then
    warn("not in a game yet")
    return
end

if not isfolder("dump") then
    makefolder("dump")
end

local scripts = getscripts(me)
print(#scripts .. " scripts under " .. me.Name)

for index, script in ipairs(scripts) do
    local bytecode = getscriptbytecode(script)
    if #bytecode > 0 then
        local source = decompile(bytecode)
        if not source:find("^Decompile failed") then
            local name = (script.Name:gsub("[^%w_]", "_"))
            writefile("dump/" .. index .. "_" .. name .. ".lua", source)
        end
    end
end

Functions

getscripts([root])

Returns every LocalScript, Script and ModuleScript under root, including root itself.

Parameters:

  • root: Instance (optional, default game) - Instance to start from. Omitted or nil uses the DataModel. Any other non-Instance value (including false) raises an argument error.

Returns:

  • scripts: table - Array of script Instances in the order they were found. Empty when the DataModel cannot be read (no game loaded).
  • The walk stops after 8000 scripts or after visiting 120000 instances, whichever comes first, so very large games give a partial list.
  • An instance with more than 10000 children is treated as having none, so nothing below it is visited.
lua
for _, script in ipairs(getscripts(game:GetService("ReplicatedStorage"))) do
    print(script.ClassName, script:GetFullName())
end

getscriptbytecode(script)

Returns a script's compiled bytecode as a binary string, or "" if it can't be read.

Parameters:

  • script: Instance - A LocalScript, Script or ModuleScript.

Returns:

  • bytecode: string - The raw bytes exactly as the client stores them. "" when script is not an Instance, is not one of the three script classes, has no bytecode on the client, cannot be read, or is larger than 16 MiB.
  • May be Roblox's compressed RSB1 format. decompile handles it.
  • Passing a non-Instance value never raises; it returns "".
lua
local scripts = getscripts()
for i = 1, math.min(#scripts, 10) do
    local bytes = getscriptbytecode(scripts[i])
    if #bytes > 0 then
        print(scripts[i]:GetFullName(), #bytes .. " bytes", string.format("first byte 0x%02X", bytes:byte(1)))
    end
end

decompile(source)

Turns bytecode into Luau source. Falls back to a disassembly, then a failure report. See Output forms.

Parameters:

  • source: Instance | string - A script Instance or a bytecode string.

Returns:

  • text: string - Decompiled source, a disassembly listing, or a message. Never nil.
  • Returns -- decompile: no bytecode when nothing can be read.
  • decompile(getscriptbytecode(s)) matches decompile(s).
  • The MCP server uses this function for its script reading tool.
lua
local target = game:GetService("ReplicatedStorage"):FindFirstChild("Shared")
if target then
    local text = decompile(target)
    if text:find("^Decompile failed") then
        warn(text)
    else
        print(text)
    end
end

getsenv(script)

Compatibility stub that always returns an empty table. Use getgc or findgc to inspect game state.

Parameters:

  • script: Instance (optional) - Ignored.

Returns:

  • env: table - A fresh empty table on every call.
lua
local env = getsenv(game)
print(next(env) == nil)

Output forms

decompile reports problems in its return value rather than by raising. The start of the text tells you which path produced it:

Text starts withMeaning
Luau sourceThe decompiler succeeded.
; Luau bytecodeThe decompiler failed but the disassembler parsed the bytecode. The listing gives the bytecode version, the string and function counts, and for every function its constants and its instructions with their operands.
Decompile failed.Neither path could handle the bytes. The text includes the decompiler's message and the disassembler's message, for example when the blob is empty or is still encrypted.
-- decompile: no bytecodeThe argument was not a string and no bytecode could be read from it.

Garbage Collector

Scan the game's Luau heap from outside Roblox, inspect its tables and closures, and tune its garbage collector.

This is the game's VM, not yours

These read the game's Luau heap through process memory, not clover's own state. Results are proxies that point at game addresses. See Proxy objects.

Only on the supported client build

Only works on the supported Roblox build. Otherwise most functions return nil, "no game lua state".

Writes go straight into game memory

Writing to proxies, debug.setupvalue and setgc change the game's heap directly with no safety checks. A wrong write corrupts the game.

Scans are synchronous and proxies are never freed

Scans block the VM until done, and getgc(true) can create 120000 proxies. Don't scan every frame.

gc-ammo.lua
local info, problem = getgc_info()
if not info then
    warn("game VM not available: " .. tostring(problem))
    return
end
print(string.format("game heap: %d KB, collector running: %s", info.kb, tostring(info.isrunning)))

local iter, configs = findgc("MaxAmmo")
if iter then
    for _, config in ipairs(configs) do
        local current = config.MaxAmmo
        if type(current) == "number" then
            print("MaxAmmo was " .. current)
            config.MaxAmmo = 999
        end
    end
end

Proxy objects

Results are proxies: empty tables that store a game address. Each access re-checks the address. Every read makes a new proxy, so two proxies for the same object are never ==.

Table proxies

OperationBehaviour
t.key, t["key"]Scans the game table's hash part for the string key. Returns nil, a boolean, a number, a string (strings over 65536 bytes read as nil), a table proxy or a function proxy. Any other value type (userdata, vector, thread, buffer) reads as nil.
t[1]A number key is converted to a string first, so this looks up the string key "1". The array part of a game table is never read.
Nested tablesA table value whose hash part is empty (a pure array or an empty table) reads as nil, as does one with more than 2^18 hash slots or more than 1000000 array slots.
t.key = valueOnly overwrites a key that already exists, and only with a number (numeric strings count as numbers) or a boolean. It replaces the old value whatever its type was. Other values, missing keys and nil are ignored without an error.
rawget(t, "key")Same as t.key; see the rawget override.
pairs(t), next(t), #tSee nothing, because the proxy itself is empty. Use getrawkeys.

Write slot cache

Writes cache the slot they find. If the game resizes the table in between, a later write can hit the wrong slot.

Function proxies

A function proxy can't be called or indexed. Use it with the debug additions. debug.getinfo(fn).addr gives the real game address.

If a closure changes after the scan, the debug functions treat its proxy as stale.

Functions

getgc([include_tables])

With true, returns an iterator over the game's tables. Without it, returns the same info table as getgc_info. Functions are never included; use getclosures.

Parameters:

  • include_tables: any (optional, default nil) - Any truthy value selects the table scan.

Returns:

  • iter: function - Iterator that yields index, proxy pairs.
  • list: table - The array of table proxies the iterator walks.
  • start: number - Always 0.
  • Returns nil, "no game lua state" when no game is loaded; check before looping.
  • local list = getgc(true) stores the iterator function. Take the second value when you want the array.
  • Skips empty and huge tables and stops at 120000 tables.
lua
local iter, list = getgc(true)
if not iter then
    warn("getgc failed: " .. tostring(list))
    return
end
print(#list .. " game tables found")

findgc(key, [value])

Returns an iterator over game tables that have the string key key (and, if given, the string value). With key nil, matches tables holding value under any key.

Parameters:

  • key: string | nil - String key to look for. Numbers are accepted and compared as their string form. nil selects the value-only search.
  • value: string (optional) - String the value must equal. Required when key is nil.

Returns:

  • iter: function - Iterator that yields index, proxy pairs.
  • list: table - The array of matching table proxies.
  • start: number - Always 0.
  • Returns nil, "no game lua state" when no game VM is found.
  • Raises findgc(key|keys) or findgc(key,value) or findgc(nil,value) when the arguments fit none of the forms, for example findgc() or findgc(nil).
  • Stops after 8000 matching tables.
lua
for _, weapon in findgc("Damage") do
    print(weapon.Name, weapon.Damage)
end

for _, entry in findgc(nil, "Sniper") do
    print("table holding Sniper", getrawkeys(entry).Name)
end

findgc(keys)

Batch form: one pass over the heap for several keys at once. Returns a plain table that maps every accepted key to an array of table proxies whose hash part has that string key.

Parameters:

  • keys: table - Array of keys (numbers are converted to strings). Empty strings and duplicates are skipped, and only the first 128 distinct keys are used.

Returns:

  • results: table - results[key] is an array of table proxies for every accepted key, possibly empty.
  • At most 400 tables are collected per key. The scan ends early once every key has 400.
  • A table that has several of the keys appears in each of their lists, as separate proxies.
  • When no game VM is found, it still returns results with empty lists, not nil and a message.
lua
local found = findgc({"Ammo", "MaxAmmo", "ReloadTime"})
for key, tables in pairs(found) do
    print(key, #tables)
end

getclosures()

Returns an array of function proxies for the game's closures.

Returns:

  • closures: table - Array of function proxies, at most 60000 across all game VMs.
  • Returns nil, "no game lua state" when no game VM is found.
  • Closures without upvalues, or with more than 62, are never listed.
  • Each proxy records its upvalue count at scan time, which debug.getupvalue, debug.getupvalues and debug.setupvalue use to detect a stale proxy.
lua
local closures, problem = getclosures()
if not closures then
    warn(problem)
    return
end
local lua_count = 0
for _, fn in ipairs(closures) do
    local info = debug.getinfo(fn)
    if info and not info.is_c then
        lua_count = lua_count + 1
    end
end
print(#closures .. " closures, " .. lua_count .. " Lua closures")

getrawkeys(proxy)

Takes a snapshot of a table proxy. Returns a new ordinary Lua table that maps every string key in the game table's hash part to its value, converted the same way as a proxy read.

Parameters:

  • proxy: table - A table proxy. Anything else, including a function proxy, gives an empty table.

Returns:

  • entries: table - Plain table of key = value. Changing it does not change the game.
  • Keys that are empty, longer than 128 bytes or not strings are skipped, and the array part is not read.
  • Entries whose value is nil or a type proxies cannot represent (userdata, vector, thread, buffer, or a table with an empty hash part) are left out.
lua
for _, config in findgc("MaxAmmo") do
    for key, value in pairs(getrawkeys(config)) do
        print(key, value)
    end
    break
end

getgc_info()

Reads collector statistics from the largest game VM (the one with the most allocated bytes). getgc() with no argument returns the same table.

Returns:

  • Returns nil, "no game lua state" when no game VM is found.
  • List counts only cover the collector's gray, grayagain and weak lists, up to 80000 objects each.
lua
local info = getgc_info()
if info then
    print("heap KB", info.kb, "state", info.gcstate, "pages", info.pages)
    for name, count in pairs(info.by_type) do
        print(name, count)
    end
end

getgc_info fields

FieldTypeDescription
kbnumberTotal bytes allocated by the game VM, divided by 1024 and rounded down. Pushed as a float.
gcstateintegerCollector phase byte from the game's global state. A VM is only accepted when this is between 0 and 4.
isrunningbooleanfalse while the GC threshold is -1 (after setgc("stop")), otherwise true.
pauseintegerThe game's gcpause setting.
stepmulintegerThe game's gcstepmul setting.
pagesintegerHeap pages on both page lists that passed the walker's checks (block sizes 40 to 1024 bytes, at most 4096 pages per list).
grayintegerObjects on the gray list.
grayagainintegerObjects on the grayagain list.
weakintegerObjects on the weak list.
totalintegergray + grayagain + weak.
LstringAddress of the game's lua_State, as hex text like 0x1A2B3C40.
GstringAddress of the game's global state, as hex text.
by_typetableCounts of the listed objects keyed by type name: nil, bool, lightuserdata, number, vector, string, table, function, userdata, thread, buffer or other. Only types with a non-zero count are present.
samplestableArray of up to 24 listed objects, each {tt = integer type tag, name = type name, addr = hex string}.

setgc(option, [value])

Reads or changes the collector settings of the largest game VM by reading and writing its global state directly. See setgc options for what each option returns.

Parameters:

  • option: string - One of the option names below. A missing option raises an argument error.
  • value: integer (optional) - Integer value for pause/setpause or stepmul/setstepmul.

Returns:

  • result: any - Depends on the option. Failures return nil and a message.
  • Returns nil, "no game lua state" when no game VM is found, before the option name is matched.
lua
local before = setgc("pause", 200)
print("gcpause was", before, "now", setgc("pause"))
if setgc("isrunning") then
    print("game heap KB", setgc("count"))
end

setgc options

OptionEffectReturns
"stop"Writes -1 into the game's GC threshold.true
"restart"Sets the GC threshold to the current total bytes.true
"count"Reads total bytes allocated.number of KB (float)
"isrunning"Reads the GC threshold.false while it is -1, else true
"pause", "setpause"With value, writes it to gcpause, clamped to 0..1000.previous gcpause (integer)
"stepmul", "setstepmul"With value, writes it to gcstepmul, clamped to 0..10000.previous gcstepmul (integer)
"collect", "step"Not supported, since a collection step must run inside the game.nil, "collect/step need ingame call"
anything elseNothing.nil, "unknown setgc option"

gcprobe()

Diagnostic dump of every game VM found: how many tables the scan sees, how many string keys they hold, and a sample of key names. It runs the full table scan once per VM, so it is slow.

Returns:

  • Returns nil, "no game lua state" when no game VM is found.
lua
local probe = gcprobe()
if probe then
    for _, vm in ipairs(probe.vms) do
        print("vm", vm.index, vm.kb .. " KB", vm.hash_tables .. " tables", vm.str_keys .. " string keys")
        print(table.concat(vm.sample_keys, ", "))
    end
end

gcprobe fields

FieldTypeDescription
vm_countintegerNumber of game VMs found (1 or 2).
vmstableArray with one entry per VM, described below.
vms[i].indexintegerWhich of the two ScriptContext VM slots it came from (0 or 1).
vms[i].kbintegerTotal bytes allocated by that VM, divided by 1024.
vms[i].hash_tablesintegerTables found by the same scan getgc(true) uses (at most 120000).
vms[i].str_keysintegerHash slots with a string key across those tables.
vms[i].GstringAddress of the VM's global state, as hex text.
vms[i].LstringAddress of the VM's lua_State, as hex text.
vms[i].sample_keystableUp to 15 non-empty string keys (at most 96 bytes each) in the order they were met.
ammo_vmintegerAlways 0.

debug additions

clover adds four functions to debug for game function proxies. With the unsafe API on, they replace the standard getinfo, getupvalue and setupvalue.

All four raise expected a game function from getgc or getclosures when the first argument is not a function proxy, and closure layout is unavailable for this Roblox build when the closure layout fails its sanity checks.

debug.getinfo(fn)

Describes a game closure. Takes no what string and returns its own set of fields.

Parameters:

  • fn: table - A function proxy.

Returns:

  • info: table - Fields is_c, what, nups, addr, env and proto; see Function info fields. nil when the address no longer holds a function.
lua
for _, fn in ipairs(getclosures() or {}) do
    local info = debug.getinfo(fn)
    if info and info.what == "Lua" and info.nups > 3 then
        print(info.addr, info.proto, info.nups)
    end
end

Function info fields

FieldTypeDescription
is_cbooleantrue for a C closure.
whatstring"C" or "Lua".
nupsintegerUpvalue count.
addrstringAddress of the closure in the game, as hex text like 0x1A2B3C40.
envtableTable proxy for the closure's environment. Absent when the environment is not a readable table with a hash part.
protostringAddress of the function prototype, as hex text. Only present for Lua closures.

debug.getupvalue(fn, index)

Reads one upvalue of a game closure. An upvalue captured by reference is followed to the shared cell it points at. Only the value is returned, not a name.

Parameters:

  • fn: table - A function proxy.
  • index: integer - Upvalue number, from 1 to the closure's upvalue count.

Returns:

  • value: any - nil, a boolean, a number, a string, a table proxy or a function proxy. Other value types and unreadable cells give nil. Also nil when fn is stale.
  • Raises upvalue index out of range (the function has N) when index is outside 1..N.
lua
local closures = getclosures() or {}
local fn = closures[1]
if fn and debug.getinfo(fn) then
    print(debug.getupvalue(fn, 1))
end

debug.getupvalues(fn)

Reads every upvalue of a game closure into an array, converted the same way as debug.getupvalue. Not part of standard Lua.

Parameters:

  • fn: table - A function proxy.

Returns:

  • upvalues: table - Array indexed 1..N. Empty when fn is stale.
  • Upvalues that read as nil leave holes, so use debug.getinfo(fn).nups for the count rather than #.
lua
for _, fn in ipairs(getclosures() or {}) do
    for index, value in pairs(debug.getupvalues(fn)) do
        if value == "Sniper" then
            print(debug.getinfo(fn).addr, index)
        end
    end
end

debug.setupvalue(fn, index, value)

Overwrites one upvalue of a game closure in game memory. For an upvalue captured by reference the shared cell is written, so every closure sharing that variable sees the change.

Parameters:

  • fn: table - A function proxy.
  • index: integer - Upvalue number, from 1 to the closure's upvalue count.
  • value: nil | boolean | number | table - New value: nil, a boolean, a number, a table proxy or a function proxy. It is required, so pass nil explicitly to clear.

Returns:

  • ok: boolean - true after the write. false when fn is stale.
  • Strings, local tables, local functions and other types raise only nil, booleans, numbers and game objects can be written.
  • Changing a collectable upvalue to a different collectable type is refused.
  • Other errors: upvalue index out of range (the function has N), that upvalue slot is not readable, that upvalue is not readable, could not read that upvalue and could not write that upvalue.
lua
for _, fn in ipairs(getclosures() or {}) do
    local values = debug.getupvalues(fn)
    if values[1] == "Sniper" and type(values[2]) == "number" then
        print("old cooldown", values[2])
        debug.setupvalue(fn, 2, 0)
    end
end

Overrides

rawget(table, key)

The global rawget is wrapped. When table is a table proxy and key is a string, the game table is read exactly like proxy[key]. In every other case the original rawget runs unchanged.

Parameters:

  • table: table - Any table, or a table proxy.
  • key: any - Key to read. Both arguments are required.

Returns:

  • value: any - The raw value.
  • Number keys are not converted here, so rawget(proxy, 1) reads the empty proxy and returns nil, while proxy[1] looks up the string key "1".
lua
for _, config in findgc("MaxAmmo") do
    print(rawget(config, "MaxAmmo"))
    break
end

FFlags

Read and change Roblox fast flags (FFlags) in the running client, and force the sky to re-render.

setfflag needs the unsafe API

setfflag raises setfflag requires Allow unsafe Lua API. unless Allow unsafe Lua API is on (see Sandbox & Safety). getfflag and reloadsky work without it.

Memory only, not saved

setfflag changes the live value in Roblox only. It is lost when Roblox restarts and not undone by a VM reset. Some flags only take effect after the client re-reads them.

Exact names

Names are exact and case-sensitive, without the FFlag/FInt prefix, for example TaskSchedulerTargetFps.

fps-cap.lua
local name = "TaskSchedulerTargetFps"
local current = getfflag(name)
print(name, "is", current)

if math.type(current) == "integer" then
    local ok, result = pcall(setfflag, name, 144)
    if ok and result then
        notify("FPS cap set to 144")
    else
        warn("setfflag failed:", result)
    end
end

Flag types

Each flag in the registry is classified by its variable type. What getfflag returns and what setfflag accepts depends on that type:

Flag typegetfflag returnssetfflag
bool (regular and Luau bool flags)booleanAccepts true/false, the numbers 1/0, or the strings "true"/"false" (any case) and "1"/"0".
int (regular and Luau int flags)integer (signed 32-bit)Accepts a whole number from -2147483648 to 2147483647, or a decimal string of digits with an optional leading + or - (at most 11 characters). Floats with a fraction, NaN and booleans are rejected.
stringstring, or nil when the text is longer than 4096 bytes or cannot be readNot settable: raises <name> is not a bool or int flag, so it cannot be set.
exists only in the remote configthe raw config text as a string (or nil if it cannot be read)Raises <name> exists only in the remote config and has no variable in this client.
unrecognised typenilRaises <name> is not a bool or int flag, so it cannot be set.

Functions

getfflag(name)

Looks up a fast flag by name and returns its current value from Roblox memory, converted by the flag's type.

Parameters:

  • name: string - Exact, case-sensitive flag name.

Returns:

  • value: boolean | integer | string | nil - The flag's value, or nil when no flag has that name, the type is not recognised, or the value cannot be read.
  • An unknown name returns nil; it does not raise.
  • Raises one of the registry errors when Roblox is not attached or the flag registry cannot be used.
lua
local value = getfflag("TaskSchedulerTargetFps")
if value == nil then
    print("flag not found")
else
    print("TaskSchedulerTargetFps =", value)
end

setfflag(name, value)

Writes a new value into a bool or int fast flag, then reads it back. Values are converted as described in Flag types.

Requires the unsafe API.

Parameters:

  • name: string - Exact, case-sensitive flag name.
  • value: boolean | number | string - The new value. Omitting it or passing nil raises setfflag needs a value.

Returns:

  • ok: boolean - true when reading the flag back gives the value that was written, false otherwise.
  • Raises Unknown flag: <name> when no flag has that name.
  • Raises <name> is a bool flag; use true, false, 1 or 0. or <name> is an int flag; use a whole number that fits 32 bits. when the value cannot be converted.
  • Raises <name> has no valid storage. when the flag's variable is not inside the Roblox module.
  • Also raises the registry errors.
lua
local ok, err = pcall(setfflag, "TaskSchedulerTargetFps", "240")
if not ok then
    warn(err)
end

reloadsky()

Makes Roblox rebuild the skybox and lighting on the next frame.

  • Does nothing when Roblox is not attached, when this Roblox build isn't supported yet, or when the render view cannot be found.
lua
reloadsky()

Errors

Both getfflag and setfflag raise these when the flag registry cannot be used:

MessageMeaning
Roblox is not attached.No Roblox process is attached.
The flag registry is not available on this Roblox build.This Roblox build isn't supported yet.
The flag registry is not initialised yet.The registry pointer is not set yet, for example very early in startup.
The flag registry layout does not match this Roblox build.The registry does not look like a valid flag table on this build.

Teleport

Queue Lua source that runs in a fresh VM after Roblox moves you to another server or place.

The VM restarts

After a teleport, clover starts a fresh VM before running queued scripts. Nothing from the old VM survives, so queued scripts must set everything up again.

Nothing queued, no restart

If the queue is empty, a teleport does not touch the VM. Your scripts keep running, and the game and workspace globals are pointed at the new DataModel as soon as it appears.

Queue before you teleport

Scripts aren't compiled when queued. Check them with load first so a syntax error doesn't waste the teleport.

after-teleport.lua
local source = [[
print("arrived in place", game.PlaceId, "server", game.JobId)
notify("teleport script ran")
]]

local compiled, problem = load(source)
if not compiled then
    error(problem)
end

queue_on_teleport(source)
print("queued; it will run after the next teleport")

What happens on a teleport

While at least one script is queued, the engine samples the game on every pass of the script thread (roughly every 16 ms, only while the master switch is on). It counts a teleport when any of these change compared with the game it saw before:

  • the DataModel, the LocalPlayer, game.PlaceId or game.GameId (each only when both the old and new values are known)
  • workspace.DistributedGameTime jumps back by more than 5 seconds
  • the DataModel disappears for at least 0.25 seconds and then comes back
  1. Wait for the new game

    After a teleport is detected, the engine waits until the DataModel, the LocalPlayer and game.PlaceId have all been present for 1.5 seconds without interruption. If that never happens, it goes ahead anyway 30 seconds after the teleport was detected.

  2. Take the queue

    Every queued script is removed from the queue, which is now empty. The log shows teleport detected; restarting the VM to run the queued scripts.

  3. Restart the VM

    On its next pass the script thread closes the VM, opens a fresh one with the full API and clears the drawing buffers. The log shows vm reset complete and running N queued teleport script(s).

  4. Run the scripts

    The scripts run once each, in the order they were queued, as text chunks in their own coroutines, so they may yield with task.wait. A compile or runtime error is logged and the next script still runs. A script that should also run after the following teleport has to queue itself again.

The queue is emptied without running anything when the attached Roblox process changes, when no game is loaded for 120 seconds, when clear_teleport_queue is called, or when the VM is reset for any other reason, including turning the unsafe API toggle on or off.

Limits

LimitValueError when exceeded
Scripts in the queue32queue_on_teleport already holds 32 scripts
Size of one script256 KB (262144 bytes)queue_on_teleport script is larger than 256 KB
Total size of the queue1 MB (1048576 bytes)queue_on_teleport queue is larger than 1 MB
Empty sourcenot allowedqueue_on_teleport needs a non-empty script
TimingValue
New game must be stable for1.5 s
Run anyway after30 s
Queue dropped after no game for120 s
Shortest DataModel gap that counts as a teleport0.25 s
DistributedGameTime drop that counts as a teleportmore than 5 s

Functions

queue_on_teleport(source)

Adds Lua source to the teleport queue. After the next detected teleport the VM restarts and runs every queued script once. Returns nothing.

Parameters:

  • source: string - Lua source text. It is stored as-is and only compiled when it runs, as a text chunk (precompiled bytecode is not accepted).

Aliases: queueonteleport

  • Raises an error for empty source or when a limit is exceeded.
  • The queue lives outside the Lua VM, so it survives the VM restart it triggers, but a manual VM reset empties it.
lua
if isfile("rejoin.lua") then
    queue_on_teleport(readfile("rejoin.lua"))
end

clear_teleport_queue()

Removes every queued script and stops watching for a teleport until something is queued again. Takes no arguments and returns nothing.

lua
queue_on_teleport("print('one')")
clear_teleport_queue()

Running on every teleport

Because each queued script runs only once, a script that should follow you through several teleports re-queues itself. Keep it in the workspace folder (see file) and queue it from the file:

rejoin.lua
print("now in place", game.PlaceId)
queue_on_teleport(readfile("rejoin.lua"))

Compatibility shims

Fallbacks for function names other executors provide, and what each really does here.

How the shims are defined

Shims are defined as name = name or fallback at startup, so they never replace engine functions. You can overwrite them.

Several shims are approximations

Several are approximations: getrawmetatable respects __metatable, gethiddenproperty can't see hidden properties, rconsole* writes to the normal log, and fireclickdetector does nothing.

compat-check.lua
print(getexecutorname(), getfpscap())
print("print is a C function:", iscclosure(print))
print("same object:", compareinstances(game:GetService("Players"), game:FindFirstChild("Players")))

local helpers, problem = loadfile("lib/helpers.lua")
if helpers then
    helpers()
else
    rconsolewarn("helpers not loaded: " .. tostring(problem))
end

Metatables and closures

getrawmetatable(object)

Returns getmetatable(object). This is not a raw read: if the metatable has a __metatable field, that field is returned instead of the metatable.

Parameters:

  • object: any - Any value.

Returns:

  • table | any | nil - The metatable, the value of its __metatable field, or nil when there is no metatable.
  • Engine objects (Instances, Vector3, Drawing objects and so on) have no __metatable field, so for them this is the real metatable.
  • With the unsafe API on, the full debug library is loaded and debug.getmetatable(object) bypasses __metatable.
lua
local mt = getrawmetatable(Vector3.new())
print(type(mt.__index), type(mt.__add))

setrawmetatable(object, metatable)

Sets the metatable and returns object. Falls back to debug.setmetatable when a locked metatable blocks setmetatable.

Parameters:

  • object: any - The value whose metatable to set.
  • metatable: table | nil - The new metatable, or nil to remove it.

Returns:

  • any - object.
  • The fallback needs the unsafe API. Without it, protected tables and non-tables raise an error.
lua
local point = setrawmetatable({x = 1}, {__tostring = function(self) return "point " .. self.x end})
print(tostring(point))

compareinstances(a, b)

Returns a == b. Two Instance values compare equal when they wrap the same Roblox object address, so this is the same as using == on Instances in clover.

Parameters:

  • a: any - First value.
  • b: any - Second value.

Returns:

  • boolean - Whether the values are equal.
lua
local players = game:GetService("Players")
print(compareinstances(players, game:FindFirstChild("Players")))

islclosure(func)

Returns true if func is a Lua function. It works by calling string.dump(func) in pcall, which only succeeds for Lua functions.

Parameters:

  • func: any - The value to test.

Returns:

  • boolean - true for Lua functions, false for C functions and for anything that is not a function.
lua
print(islclosure(function() end), islclosure(print), islclosure(42))

iscclosure(func)

Returns true if func is a C function, using the same string.dump test as islclosure.

Parameters:

  • func: any - The value to test.

Returns:

  • boolean - true for C functions, false for Lua functions and for anything that is not a function.
  • Engine functions written in C return true; parts written in Lua (these shims, Clover:CreateWindow, ...) return false.
lua
print(iscclosure(print), iscclosure(getrawmetatable))

getexecutorname()

Returns "clover", the first value of identifyexecutor.

Returns:

  • string - "clover".

Loading files

loadfile(path)

Compiles a workspace file without running it. Errors show the file name.

Parameters:

  • path: string - Path relative to the workspace folder. The same path rules as readfile apply.

Returns:

  • chunk: function - The compiled chunk, or nil on failure.
  • problem: string - Only on failure: the readfile error (readfile: the file does not exist or the path is not allowed) or the syntax error.
  • Only text source is accepted; precompiled bytecode is refused.
  • The chunk runs in the shared global environment when you call it.
lua
local chunk, problem = loadfile("lib/helpers.lua")
if not chunk then
    warn(problem)
    return
end
local helpers = chunk()

dofile(path)

Loads a workspace file like loadfile and runs it, returning its results.

Parameters:

  • path: string - Path relative to the workspace folder.

Returns:

  • ...: any - The values returned by the chunk.
lua
local ok, settings = pcall(dofile, "configs/settings.lua")
if ok then
    print("loaded settings", settings)
end

Console

clover has no separate console window. These shims write to the normal clover log or do nothing.

rconsoleprint(...)

Calls the global print with the same arguments.

Parameters:

  • ...: any - Values to log.

Aliases: rconsoleinfo

lua
rconsoleprint("hello from rconsoleprint")

rconsolewarn(...)

Calls the global warn with the same arguments.

Parameters:

  • ...: any - Values to log.

Aliases: rconsoleerr

  • rconsoleerr is the same function, so error text is logged as a warning. Use clover.error to log at error severity.

rconsoleclear()

Does nothing. The clover log is not cleared.

rconsolename([title])

Does nothing; there is no console window to rename.

Parameters:

  • title: string (optional) - Ignored.

Aliases: rconsolesettitle

rconsoleinput()

Returns an empty string immediately. It does not wait for input.

Returns:

  • string - Always "".

printconsole(...)

The same function as the global print, captured when the VM starts.

Parameters:

  • ...: any - Values to log.

Frame rate

setfpscap([fps])

Writes fps to the TaskSchedulerTargetFps fast flag through setfflag. Returns nothing and never raises: every failure is swallowed by pcall.

Requires the unsafe API.

Parameters:

  • fps: number (optional) - Whole-number frame rate. nil, 0 or negative means 9999.
  • Needs the unsafe API toggle. Without it setfflag refuses and setfpscap silently does nothing.
  • Also does nothing when Roblox is not attached, or when the flag cannot be found or is not an integer flag.
lua
setfpscap(144)
print("cap is now", getfpscap())

getfpscap()

Reads the TaskSchedulerTargetFps fast flag with getfflag. Works without the unsafe API.

Returns:

  • number - The flag value, or 60 when it cannot be read (not attached, flag not found, or not a number). The 60 is a fixed fallback, not a measured cap.

Instances

fireclickdetector(detector, [distance], [event])

Does nothing in practice: clover Instances don't expose ClickDetector events.

Parameters:

  • detector: Instance - The ClickDetector.
  • distance: number (optional) - Ignored.
  • event: string (optional, default "MouseClick") - Name looked up on the detector.
  • To click one, use firesignal: firesignal(detector, "MouseClick", player).
  • It reads game:GetService("Players").LocalPlayer first, so it raises an error while no game is attached.
lua
local button = workspace:FindFirstChild("Button")
local detector = button and button:FindFirstChildOfClass("ClickDetector")
if detector then
    firesignal(detector, "MouseClick", game:GetService("Players").LocalPlayer)
end

gethiddenproperty(object, name)

Returns object[name], false. It can't read hidden properties, only what the bridge supports.

Parameters:

  • object: Instance - The object to read from.
  • name: string - Property name.

Returns:

  • value: any - The looked-up value, or nil.
  • isHidden: boolean - Always false.
lua
local value = gethiddenproperty(workspace, "DistributedGameTime")
print(value)

sethiddenproperty(object, name, value)

Runs object[name] = value inside pcall and returns whether it raised no error.

Parameters:

  • object: Instance - The object to write to.
  • name: string - Property name.
  • value: any - New value.

Returns:

  • boolean - true when the assignment did not raise. Instances ignore unknown property names without an error, so true does not prove that anything was written.

getinstances()

Returns game:GetDescendants(): every Instance under the DataModel, in depth-first order. Returns an empty table when that call fails, for example while no game is attached.

Returns:

  • table - Array of Instances, at most 50000 (the GetDescendants limit).
  • Instances parented to nil are not included; getnilinstances does not exist.
lua
local count = 0
for _, instance in ipairs(getinstances()) do
    if instance.ClassName == "RemoteEvent" then count = count + 1 end
end
print(count, "RemoteEvents")

Encoding aliases

The shims add these extra names for the base64 helpers. They are documented in full on the crypt page.

NameSame asNotes
crypt.base64encode(data), crypt.base64.encode, base64_encode, base64.encodebase64encodeStandard alphabet with = padding. See crypt.base64encode.
crypt.base64decode(data), crypt.base64.decode, base64_decode, base64.decodebase64decodeSkips = and any character outside the alphabet instead of failing. See crypt.base64decode.
crypt.generatebytes([count])base64encode(crypt.random(count or 16))Base64 of count random bytes (0 to 1048576). See crypt.generatebytes.
crypt.generatekey()base64encode(crypt.random(32))A 44-character base64 string of 32 random bytes. See crypt.generatekey.

Clipboard

There is no global setclipboard or toclipboard. The shims only define toclipboard when a global setclipboard already exists, and none does. Use utility.SetClipboard(text) (the same function as clover.setclipboard), which needs the unsafe API. See utility.

Not available

These common executor functions are not defined at all, so calling them raises attempt to call a nil value. Shared scripts should check first, for example if hookfunction then ... end.

  • hookfunction, hookmetamethod, newcclosure, checkcaller, getnamecallmethod
  • getrenv, getnilinstances, getconnections, cloneref
  • firetouchinterest
  • setreadonly, isreadonly, table.freeze
  • setthreadidentity, getthreadidentity