Documentation
Modes, options, macros and performance advice for getting the most out of LuaFlux.
Getting started
LuaFlux turns a readable script into one that only the interpreter can follow. It supports standard Lua and Luau, including Roblox scripts. Most builds finish in a few seconds.
- Paste your script into the editor on the home page.
- Pick a mode. If you are unsure, start with Ares.
- Press Obfuscate, then copy or download the result.
- Test the output in the same environment you plan to ship to.
Keep your original source. Obfuscated output cannot be turned back into your code.
Modes
The mode is the main choice you make for every build. Stronger modes produce heavier output, lighter modes run faster and stay smaller.
| Mode | Best for | Output |
|---|---|---|
onyx | Roblox scripts that must not be tampered with. | Heaviest |
atlas | Code that depends on its environment, such as anti cheat and server validation. | Heaviest |
ares | Everyday protected scripts. A solid default. | Heavy |
heavy | Larger scripts that still need strong protection. | Medium |
normal | Larger scripts where speed starts to matter more. | Light |
weak | Big scripts with a tight performance budget. | Lighter |
rename, minimify | A light pass over names and formatting when you only need a smaller file. | Lightest |
Options
Extra settings you can pass along with a build. Defaults suit most scripts.
| Option | Effect |
|---|---|
--seed N | Makes a build reproducible. The same script, mode and seed give the same output. |
--multiline | Spreads the output over several lines instead of one. |
--ascii | Limits the output to plain ASCII characters. |
--wset N | Trades memory use against speed. Each mode has a sensible default, so most people never change it. |
Macros
Macros are special calls you place in your script. LuaFlux replaces them while it builds, so they never appear in the output. Use them to protect specific values or to control how a part of your script is handled.
Add shims so your script still runs
Outside of LuaFlux, a macro is just an undefined function. Declare a shim for each macro you use. A shim is a global function that returns its arguments, wrapped in a guard that is removed during the build.
if not FLUX_OBFUSCATED then
function FLUX_ENCSTR(...) return ... end
function FLUX_ASSUME(...) return ... end
function FLUX_OPAQUE(...) return ... end
endEvery name starting with FLUX is reserved. Declaring one as a local, or calling a macro that does not exist, stops the build with an error.
Values
| Macro | Result |
|---|---|
FLUX_OBFUSCATED | true in the protected build. Use it in guards like the one above. |
FLUX_LINE | The line number of the call in your source. |
Protection
| Macro | What it does |
|---|---|
FLUX_ENCSTR(s)FLUX_ENCSTR(s, key, check) | Encrypts a string. With three arguments the string is also tied to a runtime value, and only reads correctly when check matches. |
FLUX_ENCBUF(s)FLUX_ENCBUF(s, key, check) | Same as FLUX_ENCSTR, but returns a Luau buffer. Luau only. |
FLUX_ENCNUM(n)FLUX_ENCNUM(n, bindings) | Encrypts a number. bindings is a table such as { [expr] = expected } with up to 8 entries. |
FLUX_ASSUME(expr, expected) | Locks the build to a constant. If expr does not equal expected at runtime, the script stops. |
FLUX_PRECHECK(fn, keys) | Runs fn and stops the script if its result is not one of the expected keys. Accepts one key or a table of keys. |
FLUX_REWRITE(expr) | Rewrites an expression into a harder to read form that returns the same result. |
FLUX_OPAQUE(bool) | Returns the boolean you pass in, in a way that is hard to predict. Adds noise to control flow at no risk. |
FLUX_CRASH() | An expression that raises an error that cannot be recovered from. |
Older spellings still work: FLUX_STRENC, FLUX_BUFENC and FLUX_NUMENC.
Performance
| Macro | What it does |
|---|---|
FLUX_NO_VIRTUALIZE(fn) | Keeps a function protected but tuned for speed. Use it on code that runs often. |
Example
local token = FLUX_ENCSTR("secret", 0x1234, userId)
local level = FLUX_ENCNUM(42, { [playerLevel] = 5, [gameId] = 9 })
if FLUX_ASSUME(game.PlaceId, 123456789) then
print("ok")
end
local magic = FLUX_REWRITE(0xDEADBEEF)Performance
Protection always costs something. The goal is to spend it where it matters and keep everything else fast.
Choose the lightest mode that fits
Heavier modes are for code you cannot afford to lose. Everything else runs better on Heavy, Normal or Weak.
Leave non sensitive code out
Interface code, rendering and physics rarely need protection. Keep them in a separate file and obfuscate only the logic that matters.
Wrap hot functions
FLUX_NO_VIRTUALIZE keeps a function protected while making it much cheaper to run.
local hot = FLUX_NO_VIRTUALIZE(function(a, b)
return a + b * 2
end)Tidy your source first
Repeated lookups get expensive once protected. Store them in a local before you build.
Before
a.b.c.d.e.f.g.x = 1
a.b.c.d.e.f.g.y = 2
a.b.c.d.e.f.g.z = 3After
local t = a.b.c.d.e.f.g
t.x = 1
t.y = 2
t.z = 3Watch what runs every frame
Overhead shows up where code runs most. If it is not sensitive, keep it out, or wrap it in FLUX_NO_VIRTUALIZE.
RenderStepped,HeartbeatandSteppedconnectionsUserInputServicehandlers such asInputBeganandInputChanged- Metamethod hooks like
__index,__namecalland__newindex - Tight loops with short
wait()ortask.wait()intervals - Events that fire many times a second
Roblox
Instance attributes work as usual. SetAttribute, GetAttribute and GetPropertyChangedSignal need no changes in your code.
local part = workspace.Part
part:SetAttribute("Health", 100)
local hp = part:GetAttribute("Health")
part:GetPropertyChangedSignal("Health"):Connect(function()
print("health changed:", part:GetAttribute("Health"))
end)Write attribute names as plain string literals so they are protected with the rest of your strings. Names built at runtime are left as they are. When a value must depend on who is running the script, wrap the name in FLUX_ENCSTR.
local key = FLUX_ENCSTR("SessionToken", 0x99, userId)
part:SetAttribute(key, tokenValue)Changelog
v1.4 Latest
- ImprovedMore variation between builds of the same script.
- ImprovedLuau compatibility.
- ImprovedCleaner error output.
v1.3
- AddedThe
--wsetoption. - AddedLuau parsing for casts, variadic types and function types in return position.
- ImprovedMemory use and general stability.
v1.2
- ImprovedHeavy, Normal and Weak now offer stronger protection while staying light.
- AddedThe
--multilineand--seedoptions. - AddedLine numbers in build errors.
v1.1
- ImprovedReworked core for better speed and protection.
- ImprovedOnyx received its full protection set.
- FixedStricter handling of numeric edge cases.
FAQ
My script errors when I run it without obfuscating
Macros need shims in your source. Declare each macro you use as a global function that returns its arguments, inside an if not FLUX_OBFUSCATED then block. See the Macros section for the full pattern.
Why is my name reported as reserved?
Anything starting with FLUX belongs to LuaFlux. A call such as FLUX_DO_SOMETHING() fails because it is not a real macro, and local FLUX_NAME = 1 fails because the prefix is reserved. Rename your variable and stick to the documented macros.
My output is too slow
Try a lighter mode, then follow the tips in the Performance section. Moving non sensitive code out of the build and wrapping hot functions in FLUX_NO_VIRTUALIZE fixes most cases.
Are my scripts stored?
No. Scripts are processed and discarded as soon as the output is returned.
Can I use FLUX_STACKALLOC?
Not at the moment. Use a plain table, or wrap the function in FLUX_NO_VIRTUALIZE.
Still stuck?
Our team answers setup questions on Discord.