vodka / Protection
How to obfuscate and protect a Luau script. Near native by default, stronger on request.
A practical guide to protecting a Luau script: the default source transformations that run near native, the opt-in bytecode VM, and how to measure the cost before you ship.
The short version
Protection is a build, not a single switch.
Protecting a Luau script means producing a separate, hardened artifact from your private source and shipping that instead. On vodka this is one build with layers: by default it rewrites your source so the output runs within noise of the original, and an opt-in mode adds a bytecode virtual machine when a script needs it. You keep the original, players get the protected version.
Set your expectations honestly at the start. Obfuscation raises the cost of reading and copying your code. It does not make a delivered value permanently secret, because an authorized client still receives the code it runs. That boundary is covered in Roblox script obfuscation explained; this guide is the how-to.
Applied by default
The default transformations, built for Luau.
Every build runs a set of source rewrites with no toggle required. They change how the program is written without changing what it does:
- Encoded constants. Strings and numbers are replaced with expressions whose decode key is derived at runtime, so literal secrets are not sitting in plain text.
- Flattened control flow. The shape of supported code is rearranged so it is harder to read top to bottom.
- Renamed locals. Local and upvalue names are rewritten with scope awareness, so meaningful names do not survive.
- Minified, watermarked output. The result is compacted and carries a per-build identifier.
Because it targets Luau, the build preserves what Luau programs depend on: type annotations are kept, string interpolation is handled at the source level, and globals, table fields, method names and type names are never renamed. There is a Lua 5.1 compatibility mode when you need it. See the Luau obfuscator page for the specifics.
Opt in, per script
Maximum protection and the bytecode VM.
When secrecy matters more than speed, turn on Maximum protection for that script. Supported functions are compiled to bytecode and interpreted by a generated per-function virtual machine, with self-integrity anti-tamper, on top of the default transformations. Functions outside the supported set keep native execution, so behavior is preserved.
It is off by default for a reason: interpreting bytecode is slower than running native Luau, and that cost shows up most on hot or looping code. The honest rule is to enable it where the logic is worth hiding and leave it off where the script spends its time. The benchmarks publish the overhead with the commands to reproduce it.
Measure it
Check the cost on your own hot path.
Do not guess at the runtime cost, and do not take a vendor's single number as yours. Protection overhead depends on what your script does. A UI script that reacts to clicks will not feel the VM; a render-step loop or a tight physics routine might.
Measure before and after on the code you actually run. Wrap your hot path in an os.clock() loop, or watch it in the Roblox MicroProfiler, and compare the default build against Maximum protection. If a section is too expensive with the VM on, you can keep the VM for the sensitive functions and leave the hot loop on the default transformations.
Before it ships
A build that passes is not a build you tested.
Every protected artifact is parsed and compiler-checked before it can publish, and a failed or cancelled build never replaces your working release. But a valid build only proves the artifact compiles, not that your script behaves in its real environment.
Test the published release where it will run, with its dependencies, callbacks, error paths and any key expiry, before you share it. Read what each protection layer does for the full reference, and if you plan to gate access, pair this with how to add a key system.
Questions
A few things worth knowing.
01Will obfuscation slow my script down?
The default source transformations run within noise of native Luau. The opt-in bytecode VM adds interpreter cost that can be noticeable on hot or looping code, which is why it is off by default. Measure your own hot path with an os.clock() loop or the MicroProfiler before enabling it.
02Does protection change how my script behaves?
It should not. Supported constructs are differential-tested against the real Luau runtime, and anything outside the supported set falls back to native execution so behavior is preserved. You should still test your own release in its target environment, because a passing build is not a behavior guarantee.
03Can obfuscation hide an API key or password in my script?
No. An authorized client receives and runs the code, so any value you ship inside it is recoverable with effort. Obfuscation raises the cost of casual inspection; it does not make a delivered secret permanent. Keep credentials and access decisions on a server.
04Should I always turn on Maximum protection?
No. Use it where the logic is worth hiding and the runtime cost is acceptable. For many scripts the default transformations are the right trade. You can even run the VM on sensitive functions while keeping a hot loop on the default build.
Keep reading
Where to go next.
The Roblox obfuscator
Encoded constants, flattened control flow and an opt-in bytecode VM, in the browser with nothing to install.
Protection, layer by layer
The full reference on what vodka applies to your code and where client-side protection ends.
Reproducible benchmarks
Protection resistance and runtime overhead, measured with the exact commands to reproduce them.
Keep building
Your next release starts here.
Bring your original source. Make the workspace your own.