Docs
Panda-K ObfuscatorPanda-K Macro Documentation
On this page

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

Authentication macros such as 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_STRING shadows 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))
end

Value 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.

Compile-time values
MacroProtected buildDevelopment header
PANDA_OBFUSCATEDtruefalse
PANDA_LINEOriginal source line of this use0
PANDA_VERSION"3.5.0""development"
PANDA_MACRO_API2 (the applied macro API version)0
PANDA_BUILD_ID16 hexadecimal characters per build; reproducible with the same build seed"development"

Encrypted constants and cache controls

Call macros
SignatureArgumentsResult and support
PANDA_ENCRYPT_STRING(s)One string literalReturns 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 expressionReturns 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 allowedReturns the number, decoded on each evaluation. VM and native Luau.
PANDA_CLEAR_STACK()No argumentsReturns nil. Clears dead scratch registers, decrypted-string caches and decoded image chunks. VM only; does not erase live locals.
PANDA_CLEAR_CONSTANT_CACHE()No argumentsReturns 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

Function policies
SignatureArgumentsBehavior
PANDA_NO_VIRTUALIZE(fn)Anonymous function literalEmits readable Luau outside the VM. Luau output only. Captured outer locals remain shared with VM code.
PANDA_INLINE(fn)Function argumentRequest 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 literalSets the VM function's optimization level. Level 3 is reserved and rejected.
PANDA_PROTECT(level, fn)Literal "minimal", "balanced" or "strong" and a function literalminimal 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 literalSets ordinary string-cache capacity for the VM function. Zero decodes ordinary strings on each use.
PANDA_CFF(fn)Anonymous function literalRequests 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 equivalents
DirectiveEffect
--!panda:nativePANDA_NO_VIRTUALIZE
--!panda:optimize 2PANDA_OPTIMIZE; levels 0, 1, 2
--!panda:protect strongPANDA_PROTECT; minimal, balanced, strong
--!panda:cache 0PANDA_CACHE_STRINGS; 0–256
--!panda:cffForce flattening for the function
--!panda:nocffExclude the function from flattening
--!panda:inlinePANDA_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.

pandak-development.luau
--[[
  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.

Accepted aliases
AliasCanonical macro
PK_OBFUSCATED / PK_LINE / PK_VERSION / PK_BUILD_IDPANDA_OBFUSCATED / PANDA_LINE / PANDA_VERSION / PANDA_BUILD_ID
PK_NO_VIRTUALIZE / PK_INLINE / PK_CFF / PK_CLEAR_STACKPANDA_NO_VIRTUALIZE / PANDA_INLINE / PANDA_CFF / PANDA_CLEAR_STACK
PK_ENC_STR / PK_ENC_NUMPANDA_ENCRYPT_STRING / PANDA_ENCRYPT_NUMBER
LPH_OBFUSCATED / LPH_LINE / LPH_NO_VIRTUALIZEPANDA_OBFUSCATED / PANDA_LINE / PANDA_NO_VIRTUALIZE
LPH_ENCSTR / LPH_STRENCPANDA_ENCRYPT_STRING
LPH_ENCNUM / LPH_NUMENCPANDA_ENCRYPT_NUMBER
MV_OBFUSCATED / MV_LINE / MV_OMIT / MV_INLINEPANDA_OBFUSCATED / PANDA_LINE / PANDA_NO_VIRTUALIZE / PANDA_INLINE
MV_ENC_STR / MV_CFFPANDA_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.

Diagnostic codes
CodeMeaning / next step
PM001Wrong argument count or kind. Check the signature.
PM002Invalid value, non-literal argument or missing function literal.
PM003Required macro API does not match the build.
PM004Unsupported dialect, backend or argument form; includes reserved optimize level 3.
PM005The same function policy was set more than once.
PM006Inlining request was not applied; the function still runs normally.
PM007Unknown or unsupported reserved macro name or directive.
PM008Legacy API-1 compatibility behavior was used.
PM009Statement macro or directive is in the wrong location.
PM010Assignment to a reserved macro name outside the supported development guard.
PM011Call/wrapper macro used as a value. Invoke it directly instead.
PC001Compatibility option accepted without an implementation.
PC002Function could not be flattened; inspect the engine diagnostic.
PC003Native function requested strong guards that cannot cover it.
PV001Internal 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.