For Developers · Panda-K
Panda-K Macro Documentation
Complete reference for Panda 3.5.0, macro API 2: compile-time values, encrypted constants, function policies, development fallbacks and diagnostics. Works with both dashboard protection levels.
How macros work
Panda recognizes supported global macro names while compiling your script. No runtime macro library is required in the protected output. A macro changes code generation; it is not a callable network API or an authentication check.
PandaAuth macros are a separate feature
PANDA_AUTHENTICATE, PANDA_AUTHENTICATE_PREMIUM and PANDA_GETKEY belong to PandaAuth's upload preprocessing. They are not Panda-K compiler macros. An upload option that skips authentication macro expansion does not define or disable the compiler macros below.- Use the bare global name. A local variable or parameter named
PANDA_ENCRYPT_STRINGshadows the macro and turns the call into an ordinary function call. - Text inside strings, comments and table field names is not a macro. Do not alias a macro into a local variable or call it through
_G. - Most wrappers require an anonymous function literal. Use
PANDA_OPTIMIZE(2, function(...) ... end), not an already-declared function variable. - Function policies normally pass to child functions. Nested wrappers combine; setting the same policy twice on one function raises PM005.
Choose a build level in Balanced & Max. Check dialect restrictions in Platform Support.
Quick start
Submit this source to Panda-K. For unprotected Studio or Luau testing, first paste the development header from the section below at the top of the same script.
-- Paste the development header above this example for unprotected testing.
PANDA_REQUIRE_MACRO_API(2)
local formatScore = PANDA_OPTIMIZE(2, function(score)
return PANDA_ENCRYPT_STRING("Score: ") .. tostring(score)
end)
local validateScore = PANDA_PROTECT("strong", function(score)
return score >= 0 and score <= PANDA_ENCRYPT_NUMBER(1000)
end)
if validateScore(42) then
print(formatScore(42))
endValue macros
Read these as values, without parentheses. All five work in VM and native Luau functions. Product version and macro API version are separate contracts.
| Macro | Protected build | Development header |
|---|---|---|
PANDA_OBFUSCATED | true | false |
PANDA_LINE | Original source line of this use | 0 |
PANDA_VERSION | "3.5.0" | "development" |
PANDA_MACRO_API | 2 (the applied macro API version) | 0 |
PANDA_BUILD_ID | 16 hexadecimal characters per build; reproducible with the same build seed | "development" |
Encrypted constants and cache controls
| Signature | Arguments | Result and support |
|---|---|---|
PANDA_ENCRYPT_STRING(s) | One string literal | Returns the string; decoded on each evaluation. VM and native Luau. |
PANDA_ENCRYPT_STRING(s, key, runtimeKey) | String literal, string or safe integer literal key, runtime expression | Returns the string when the runtime key matches; otherwise raises PANDA-KEY. Luau only. |
PANDA_ENCRYPT_NUMBER(n) | One finite numeric literal; a leading minus is allowed | Returns the number, decoded on each evaluation. VM and native Luau. |
PANDA_CLEAR_STACK() | No arguments | Returns nil. Clears dead scratch registers, decrypted-string caches and decoded image chunks. VM only; does not erase live locals. |
PANDA_CLEAR_CONSTANT_CACHE() | No arguments | Returns nil. Flushes ordinary decrypted-string caches across live closures. VM and native Luau; API 2. |
Encrypted-constant macros take literals, not computed values: PANDA_ENCRYPT_NUMBER(42) is valid; PANDA_ENCRYPT_NUMBER(score) is not. Hardened constants are separate from ordinary constants of the same value and are decoded on every use, so avoid repeatedly decoding them inside hot loops.
Cache clearing removes Panda's copies. It does not erase values retained by your locals, tables, closures or other host code. Ordinary encryption keys travel with the protected program; runtime plaintext can still be inspected.
Runtime-keyed strings
The three-argument form encrypts the literal using the build-time key. At runtime your own code must supply a matching string or integer key. The macro does not fetch keys, authenticate users or contact PandaAuth for you.
-- receiveRuntimeKey is your own server integration, not a Panda API.
local runtimeKey = receiveRuntimeKey()
local message = PANDA_ENCRYPT_STRING("protected message", "build-key-example", runtimeKey)
print(message)The build-time key is not emitted by the macro. Do not also hardcode it as the runtime key elsewhere in the shipped program. Wrong keys raise Panda: runtime key rejected [PANDA-KEY]; once the correct key reaches the client, the decoded value can still be observed. This form requires Luau and bit32.
Function wrappers
| Signature | Arguments | Behavior |
|---|---|---|
PANDA_NO_VIRTUALIZE(fn) | Anonymous function literal | Emits readable Luau outside the VM. Luau output only. Captured outer locals remain shared with VM code. |
PANDA_INLINE(fn) | Function argument | Request only: there is no inliner. The function is called normally and PM006 reports that inlining was not applied. |
PANDA_OPTIMIZE(level, fn) | Literal 0, 1 or 2 and a function literal | Sets the VM function's optimization level. Level 3 is reserved and rejected. |
PANDA_PROTECT(level, fn) | Literal "minimal", "balanced" or "strong" and a function literal | minimal disables flattening, opaque predicates, junk and guards; balanced uses build defaults; strong adds internal guards and resume-point guards. |
PANDA_CACHE_STRINGS(n, fn) | Integer literal 0–256 and a function literal | Sets ordinary string-cache capacity for the VM function. Zero decodes ordinary strings on each use. |
PANDA_CFF(fn) | Anonymous function literal | Requests control-flow flattening even when the build default is off. Eligibility failures are reported as PC002. |
local compute = PANDA_CACHE_STRINGS(0, PANDA_CFF(function(value)
if value > 0 then
return PANDA_ENCRYPT_STRING("positive")
end
return PANDA_ENCRYPT_STRING("zero or negative")
end))
print(compute(3))
-- Emits readable Luau; use only for a measured performance hotspot.
local fastAdd = PANDA_NO_VIRTUALIZE(function(a, b)
return a + b
end)
print(fastAdd(2, 3))Native functions trade VM protection for speed
PANDA_NO_VIRTUALIZE emits readable Luau. These functions are not flattened or guarded by Panda's VM. VM-only wrappers and PANDA_CLEAR_STACK() are invalid inside them. Requesting strong protection on a native function reports PC003. Ordinary strings in native code are decrypted once at load; explicit hardened string macros retain their per-use behavior.Require a macro API version
PANDA_REQUIRE_MACRO_API(2)Place this as a top-level statement. Panda removes it from output. A version mismatch raises PM003. Using it inside a function is invalid (PM009). API 2 is the current default; API 1 compatibility mode leaves API-2-only names as ordinary globals.
Luau comment directives
Put each directive on its own line before a function declaration. Blank lines and comment-only lines may appear between it and the function. Directives require the Luau frontend; the --!pk: prefix is also accepted.
--!panda:optimize 2
--!panda:protect strong
--!panda:cache 0
local function validate(value)
return value == PANDA_ENCRYPT_STRING("example")
end
print(validate("example"))| Directive | Effect |
|---|---|
--!panda:native | PANDA_NO_VIRTUALIZE |
--!panda:optimize 2 | PANDA_OPTIMIZE; levels 0, 1, 2 |
--!panda:protect strong | PANDA_PROTECT; minimal, balanced, strong |
--!panda:cache 0 | PANDA_CACHE_STRINGS; 0–256 |
--!panda:cff | Force flattening for the function |
--!panda:nocff | Exclude the function from flattening |
--!panda:inline | PANDA_INLINE; request not applied, reported as PM006 |
Development header for Roblox Studio and Luau
Paste this complete block at the top of the script, before any macro use. Keep the exact if not PANDA_OBFUSCATED then ... end guard: Panda validates and removes this fallback block in protected builds.
--[[
Panda-K development macro header — macro API 2 (Panda 3.5.0)
Paste this block at the TOP of each script while developing unprotected
(Roblox Studio, luau CLI). It defines plain-Luau fallbacks with the same
observable behaviour as the protected build, except for the documented
markers: PANDA_OBFUSCATED is false, PANDA_LINE is 0, PANDA_MACRO_API is 0
and PANDA_BUILD_ID is "development".
Rules that keep compile-time recognition working:
* Do not turn these into `local` definitions: a local named PANDA_*
shadows the macro, so the protected build would treat it as an ordinary
function call (no encryption, no native emission).
* Do not assign them through `_G`: in Roblox `_G` is a shared table, not
the script's environment, so plain `PANDA_X(...)` would not find them.
* Keep the `if not PANDA_OBFUSCATED then ... end` guard exactly in this
form: Panda recognises it, validates it, and removes it from the build.
The fallbacks provide NO encryption, virtualization or integrity checking.
]]
if not PANDA_OBFUSCATED then
PANDA_OBFUSCATED = false
PANDA_LINE = 0
PANDA_VERSION = "development"
PANDA_MACRO_API = 0
PANDA_BUILD_ID = "development"
PANDA_REQUIRE_MACRO_API = function(_api: number) end
PANDA_ENCRYPT_STRING = function(value: string, key: (string | number)?, runtimeKey: any?): string
if key ~= nil then
-- Same contract as the protected build: the runtime key must match.
local function canon(k: any): string?
if type(k) == "number" and k == k and k % 1 == 0 and math.abs(k) < 2 ^ 53 then
return string.format("%d", k)
end
return if type(k) == "string" then k else nil
end
if canon(runtimeKey) == nil or canon(runtimeKey) ~= canon(key) then
error("Panda: runtime key rejected [PANDA-KEY]", 0)
end
end
return value
end
PANDA_ENCRYPT_NUMBER = function(value: number): number
return value
end
PANDA_CLEAR_STACK = function(): nil
return nil
end
PANDA_CLEAR_CONSTANT_CACHE = function(): nil
return nil
end
PANDA_NO_VIRTUALIZE = function<T>(fn: T): T
return fn
end
PANDA_INLINE = function<T>(fn: T): T
return fn
end
PANDA_CFF = function<T>(fn: T): T
return fn
end
PANDA_OPTIMIZE = function<T>(_level: number, fn: T): T
return fn
end
PANDA_PROTECT = function<T>(_level: string, fn: T): T
return fn
end
PANDA_CACHE_STRINGS = function<T>(_limit: number, fn: T): T
return fn
end
end
These global fallbacks let ordinary Luau run without the obfuscator. They provide no encryption or VM guards. Do not change them to local definitions or assign them through _G; either change breaks the intended recognition or Roblox environment behavior. This typed header is not a plain Lua 5.1 script.
Migration aliases and unsupported names
The following aliases are enabled by default when macroAliases is true. They use Panda's argument contracts, not every feature offered by the original product. Prefer canonical PANDA_* names for new scripts.
| Alias | Canonical macro |
|---|---|
PK_OBFUSCATED / PK_LINE / PK_VERSION / PK_BUILD_ID | PANDA_OBFUSCATED / PANDA_LINE / PANDA_VERSION / PANDA_BUILD_ID |
PK_NO_VIRTUALIZE / PK_INLINE / PK_CFF / PK_CLEAR_STACK | PANDA_NO_VIRTUALIZE / PANDA_INLINE / PANDA_CFF / PANDA_CLEAR_STACK |
PK_ENC_STR / PK_ENC_NUM | PANDA_ENCRYPT_STRING / PANDA_ENCRYPT_NUMBER |
LPH_OBFUSCATED / LPH_LINE / LPH_NO_VIRTUALIZE | PANDA_OBFUSCATED / PANDA_LINE / PANDA_NO_VIRTUALIZE |
LPH_ENCSTR / LPH_STRENC | PANDA_ENCRYPT_STRING |
LPH_ENCNUM / LPH_NUMENC | PANDA_ENCRYPT_NUMBER |
MV_OBFUSCATED / MV_LINE / MV_OMIT / MV_INLINE | PANDA_OBFUSCATED / PANDA_LINE / PANDA_NO_VIRTUALIZE / PANDA_INLINE |
MV_ENC_STR / MV_CFF | PANDA_ENCRYPT_STRING / PANDA_CFF |
The two-argument Luraph encrypted-string form and Luraph encrypted-number key tables are unsupported. Use Panda's literal forms or its three-argument keyed string form.
Unsupported reserved names include PANDA_CRASH, PK_CRASH, LPH_CRASH, MV_CRASH, PK_POISON, PK_CANARY, the PANDA_/LPH_/MV_REWRITE names, PANDA_OPAQUE, LPH_OPAQUE, LPH_JIT, LPH_JIT_MAX, LPH_ATTRIBUTES, PK_ATTRIBUTES, LPH_STACKALLOC, LPH_ENCBUF, LPH_ASSUME, LPH_NO_UPVALUES, PK_INDEX_TO_NUM, MV_INDEX_TO_NUM, MV_VM and MV_COMPRESS. These are diagnosed, not implemented protections.
Diagnostics and troubleshooting
Errors stop the build; warnings are recorded in the engine's build manifest and printed by the CLI. An integration may not display every manifest warning. Operator-configured strictMacros: true promotes macro warnings to errors; it is not a third dashboard preset.
| Code | Meaning / next step |
|---|---|
| PM001 | Wrong argument count or kind. Check the signature. |
| PM002 | Invalid value, non-literal argument or missing function literal. |
| PM003 | Required macro API does not match the build. |
| PM004 | Unsupported dialect, backend or argument form; includes reserved optimize level 3. |
| PM005 | The same function policy was set more than once. |
| PM006 | Inlining request was not applied; the function still runs normally. |
| PM007 | Unknown or unsupported reserved macro name or directive. |
| PM008 | Legacy API-1 compatibility behavior was used. |
| PM009 | Statement macro or directive is in the wrong location. |
| PM010 | Assignment to a reserved macro name outside the supported development guard. |
| PM011 | Call/wrapper macro used as a value. Invoke it directly instead. |
| PC001 | Compatibility option accepted without an implementation. |
| PC002 | Function could not be flattened; inspect the engine diagnostic. |
| PC003 | Native function requested strong guards that cannot cover it. |
| PV001 | Internal compiler verification failure. Report a minimal reproduction. |
If an unprotected script reports an attempt to call a nil value at a macro, add the development header. If a protected build treats a macro as an ordinary call, check for local shadowing, an alias disabled by configuration, a reserved name typo or an API-version mismatch.