Lua Environment & Security (Retail — Patch 12.0.0)
Comprehensive reference for the WoW Lua sandbox, security model, taint system, secure execution, timers, hooks, logging, and restricted actions.
Source: https://warcraft.wiki.gg/wiki/World_of_Warcraft_API
Secure Execution: https://warcraft.wiki.gg/wiki/Secure_Execution_and_Tainting
Lua Functions: https://warcraft.wiki.gg/wiki/Lua_functions
Current as of: Patch 12.0.0 (Build 65655) — January 28, 2026
Scope: Retail only.
Scope
This skill covers:
- Lua Sandbox — WoW's Lua 5.1 environment, restricted standard library, blocked functions
- Taint System — How addon code becomes tainted and what tainted code cannot do
- Secure Execution — Protected functions, secure frames, secure handlers
- Combat Lockdown — What addons can and cannot do during combat
- C_Timer — Timer functions (After, NewTicker, NewTimer)
- Hooks — hooksecurefunc, securecallfunction, securecallmethod
- C_RestrictedActions — Addon restriction state queries
- C_Log — Logging utilities
- FrameScript — Frame script environment, secret values, scrubbing
- Debugging — Error handling, stack traces, debugging utilities
When to Use This Skill
Use this skill when you need to:
- Understand what Lua functions are available vs blocked in WoW
- Work with or debug taint issues
- Write code that interacts with secure/protected frames
- Use timers, delayed execution, or ticker patterns
- Hook existing functions safely
- Understand combat lockdown restrictions
- Handle addon restriction states (12.0.0 instance restrictions)
- Log messages for debugging
- Work with secret values and the FrameScript sandbox
WoW Lua 5.1 Sandbox
WoW runs Lua 5.1.4 with significant modifications. The following standard library functions are blocked or removed:
Blocked Standard Functions
| Blocked |
Reason |
loadfile() |
No filesystem access |
dofile() |
No filesystem access |
io.* |
No filesystem access |
os.execute() |
No shell access |
os.exit() |
Cannot close client |
os.remove() |
No filesystem |
os.rename() |
No filesystem |
os.tmpname() |
No filesystem |
os.getenv() |
No environment access |
package.* |
No package system |
require() |
No module loading |
module() |
No module system |
newproxy() |
Removed |
getfenv() |
Limited — returns read-only |
setfenv() |
Very restricted |
collectgarbage() |
Limited modes |
Available Standard Functions
Most core Lua functions work normally:
- All
string.*, table.*, math.* functions
type(), tostring(), tonumber(), rawget(), rawset(), rawequal(), rawlen()
pairs(), ipairs(), next(), select(), unpack()
pcall(), xpcall(), error(), assert()
setmetatable(), getmetatable()
coroutine.* (full coroutine support)
os.time(), os.date(), os.clock(), os.difftime()
print() — outputs to default chat frame
WoW-Added Global Functions
| Function |
Description |
strsplit(delimiter, str [, pieces]) |
Split string by delimiter |
strsplittable(delimiter, str [, pieces]) |
Split to table |
strjoin(delimiter, ...) |
Join strings |
strtrim(str [, chars]) |
Trim whitespace |
tContains(table, value) |
Table contains value? |
tInsert(table, value) |
Insert into table (alias) |
tDeleteItem(table, value) |
Remove first occurrence of value |
tInvert(table) |
Invert key/value pairs |
wipe(table) |
Clear table (preserving reference) |
CopyTable(table [, shallow]) |
Deep or shallow copy |
MergeTable(dest, source) |
Merge source into dest |
Mixin(object, ...) |
Copy mixin methods to object |
CreateFromMixins(...) |
Create new object from mixins |
CreateAndInitFromMixin(mixin, ...) |
Create + call Init |
format(formatString, ...) |
Alias for string.format |
tostringall(...) |
Convert all args to strings |
DevTools_Dump(value, startKey) |
Dump value for debugging |
Taint System
All addon code runs as "tainted" (insecure). Blizzard UI code runs as "secure" (untainted). The taint system prevents addons from calling protected functions or modifying secure frames.
How Taint Works
- Any variable set by addon code becomes tainted
- Tainted values propagate — if tainted data flows into Blizzard code, it taints that path
- Protected functions check taint before executing — they fail if execution path is tainted
- Secure frames inherit security from their creation context
Checking Taint
-- Check if a global variable is tainted
local isTainted, source = issecurevariable("SomeGlobalVar")
-- isTainted: false = secure, true = tainted
-- source: string name of the addon that tainted it (or nil if secure)
-- Check table field
local isTainted, source = issecurevariable(someTable, "someKey")
Common Taint Pitfalls
-- WRONG — This taints the Blizzard settings table
Settings.RegisterAddOnCategory = myFunc -- TAINT!
-- WRONG — Modifying secure frame in insecure context
local btn = PlayerFrame -- This is a secure Blizzard frame
btn:SetAttribute("type", "spell") -- TAINT — can cause action blocked errors
-- RIGHT — Use hooksecurefunc for observation without tainting
hooksecurefunc("SomeBlizzardFunction", function(...)
-- Your code runs AFTER the original — doesn't taint
end)
Secure Execution & Protected Functions
Protected Function Restrictions
Functions marked #protected can only be called from:
- Secure (Blizzard) code
- Secure click handlers triggered by hardware events
- Inside
SecureActionButtonTemplate handlers
Protected functions include:
- All combat-related casting:
CastSpellByName(), CastSpellByID(), UseAction()
- Item use:
UseItemByName(), UseContainerItem() (in combat)
- Target changes:
TargetUnit(), AssistUnit(), FocusUnit()
- Movement:
MoveForwardStart(), JumpOrAscendStart()
- UI state:
SetAttribute() on secure frames (in combat)
Combat Lockdown
-- Check if in combat lockdown
if InCombatLockdown() then
-- Cannot: create/destroy secure frames, change secure attributes
-- Cannot: set points on secure frames, change parent/visibility of secure frames
-- Can: read attributes, modify non-secure frames, queue changes for later
return
end
-- Queue changes for after combat
local frame = CreateFrame("Frame")
frame:RegisterEvent("PLAYER_REGEN_ENABLED")
frame:SetScript("OnEvent", function()
-- Combat ended — safe to modify secure frames now
DoSecureFrameChanges()
end)
Secure Handlers & Templates
-- SecureActionButtonTemplate — allows protected actions via user clicks
local btn = CreateFrame("Button", "MySecureBtn", UIParent, "SecureActionButtonTemplate")
btn:SetAttribute("type", "spell")
btn:SetAttribute("spell", "Fireball")
-- When clicked by hardware event, this will cast Fireball
-- SecureHandlerBaseTemplate — run secure snippets
local frame = CreateFrame("Frame", nil, UIParent, "SecureHandlerBaseTemplate")
frame:SetAttribute("_onstate-combat", [[
-- This snippet runs in the secure environment
if newstate == "combat" then
self:Hide()
else
self:Show()
end
]])
RegisterStateDriver(frame, "combat", "[combat] combat; nocombat")
State Drivers
-- Register a state driver for automatic secure attribute updates
RegisterStateDriver(frame, "stateName", "conditionalString")
-- e.g., RegisterStateDriver(frame, "visibility", "[combat] hide; show")
UnregisterStateDriver(frame, "stateName")
C_Timer — Timer API
Wiki: https://warcraft.wiki.gg/wiki/API_C_Timer.After
Timer Functions
| Function |
Returns |
Description |
C_Timer.After(seconds, callback) |
— |
One-shot timer |
C_Timer.NewTimer(seconds, callback) |
timer |
Cancellable one-shot timer |
C_Timer.NewTicker(seconds, callback [, iterations]) |
ticker |
Repeating timer |
Timer Object Methods
local timer = C_Timer.NewTimer(5, function()
print("5 seconds elapsed")
end)
timer:Cancel() -- Cancel before it fires
local ticker = C_Timer.NewTicker(1, function()
print("Every second")
end, 10) -- Stop after 10 iterations
ticker:Cancel() -- Or cancel early
-- Simple delay (non-cancellable)
C_Timer.After(2, function()
print("2 seconds later")
end)
Hooks — Function Hooking
hooksecurefunc
The primary safe hooking mechanism. Your hook runs after the original function, without tainting it.
-- Hook a global function
hooksecurefunc("UseAction", function(slot, checkCursor, onSelf)
print("Action used:", slot)
end)
-- Hook a method on an object
hooksecurefunc(GameTooltip, "SetUnitAura", function(self, ...)
-- Runs after GameTooltip:SetUnitAura
end)
-- IMPORTANT: You CANNOT prevent the original from executing
-- IMPORTANT: You CANNOT modify the return values
-- IMPORTANT: Your hook does NOT taint the original function
securecallfunction / securecallmethod
-- Call a function in secure context (if possible)
securecallfunction(func, arg1, arg2)
-- Call a method in secure context
securecallmethod(object, "MethodName", arg1, arg2)
C_RestrictedActions — Addon Restriction State
New in 12.0.0. Tracks when addon restrictions are active (e.g., inside instances).
| Function |
Returns |
Description |
C_RestrictedActions.GetAddOnRestrictionState(type) |
state |
Current restriction state |
C_RestrictedActions.IsAddOnRestrictionActive(type) |
active |
Is restriction currently active? |
C_RestrictedActions.CheckAllowProtectedFunctions(object [, silent]) |
protectedFunctionsAllowed |
Can object call protected funcs? |
InCombatLockdown() |
inCombatLockdown |
Combat lockdown active? |
Restriction Events
| Event |
Description |
ADDON_RESTRICTION_STATE_CHANGED |
Restriction state changed (entering/leaving instance) |
PLAYER_REGEN_DISABLED |
Entering combat |
PLAYER_REGEN_ENABLED |
Leaving combat |
C_Log — Logging
| Function |
Description |
C_Log.LogMessage(message) |
Log info message |
C_Log.LogWarningMessage(message) |
Log warning |
C_Log.LogErrorMessage(message) |
Log error |
C_Log.LogMessageWithPriority(priority, message) |
Log with specific priority |
Note: ConsolePrint() was removed in 12.0.0. Use C_Log.LogMessage() instead.
FrameScript Functions
WoW provides special FrameScript functions for working with the secure/secret value system:
| Function |
Returns |
Description |
issecurevariable([table,] name) |
isSecure, taintSource |
Check taint status |
issecretvalue(value) |
isSecret |
Is value a secret? |
issecrettable(table) |
isSecretOrContentsSecret |
Is table or contents secret? |
canaccessvalue(value) |
isAccessible |
Can addon access this value? |
hasanysecretvalues(values) |
isAnyValueSecret |
Any arg secret? |
scrubsecretvalues(values) |
scrubbed |
Replace secrets with nil |
secretwrap(values) |
wrapped |
Wrap values as secrets |
mapvalues(func, values) |
mapped |
Map function over values (secret-safe) |
securecallfunction(func, ...) |
results |
Call in secure context |
securecallmethod(obj, method, ...) |
results |
Call method in secure context |
forceinsecure() |
— |
Force insecure execution |
seterrorhandler(handler) |
— |
Set global error handler |
geterrorhandler() |
handler |
Get current error handler |
Debugging Utilities
Error Handling
-- Set a custom error handler
seterrorhandler(function(msg)
-- msg is the error string
print("ERROR:", msg)
end)
-- Protected call with error handling
local success, err = pcall(function()
-- Code that might error
end)
if not success then
print("Error:", err)
end
-- xpcall with message handler
local success, err = xpcall(function()
error("something broke")
end, function(msg)
return msg .. "\n" .. debugstack(2)
end)
Debug Stack & Info
-- Get a stack trace
local stack = debugstack([thread,] [start [, count1 [, count2]]])
-- Get debug info
local info = debuglocals([thread,] [level])
-- Profile timing
debugprofilestart()
-- ... code to measure ...
local elapsed = debugprofilestop() -- microseconds
Slash Commands for Debugging
-- /dump expression — evaluates and prints
-- /run code — executes Lua code
-- /script code — same as /run
-- /console cvarName [value] — get/set console variables
Common Patterns
Deferred Initialization (Wait for Login)
local frame = CreateFrame("Frame")
frame:RegisterEvent("PLAYER_LOGIN")
frame:SetScript("OnEvent", function(self, event)
-- Safe to initialize — player is logged in
self:UnregisterEvent(event)
InitializeAddon()
end)
Safe OnUpdate Throttle
local elapsed = 0
local THROTTLE = 0.1 -- 100ms
frame:SetScript("OnUpdate", function(self, dt)
elapsed = elapsed + dt
if elapsed < THROTTLE then return end
elapsed = 0
-- Do periodic work
end)
Post-Combat Action Queue
local pendingActions = {}
local function QueueAction(action)
if InCombatLockdown() then
tinsert(pendingActions, action)
else
action()
end
end
local frame = CreateFrame("Frame")
frame:RegisterEvent("PLAYER_REGEN_ENABLED")
frame:SetScript("OnEvent", function()
for _, action in ipairs(pendingActions) do
action()
end
wipe(pendingActions)
end)
Graceful Secret Value Handling (12.0.0)
-- When values might be secret, pass them directly to widgets
local name = UnitName(unit) -- may be secret
myFontString:SetText(name) -- widgets accept secrets
-- Check if a value is secret before trying operations
if not issecretvalue(someValue) then
-- Safe to compare, do arithmetic, etc.
if someValue == "expected" then ... end
else
-- Cannot inspect — pass to UI widget directly
myWidget:SetText(someValue)
end
Gotchas & Restrictions
- No
require() — WoW has no module system. Use the TOC file to control load order. Libraries are embedded directly.
setfenv() / getfenv() — Severely restricted. Do not rely on environment manipulation.
collectgarbage() — Only "count" mode works. Cannot force GC collection.
- Taint is sticky — Once a variable is tainted, it stays tainted. Even if you set it back to the original value, the taint remains.
print() goes to chat — Unlike standard Lua, print() outputs to the default chat frame, not stdout.
- String library additions — WoW adds
strsplit, strjoin, strtrim, and strmatch as globals (in addition to string.match).
- No
os.exit() — Cannot terminate the client programmatically.
- Coroutines work — Full coroutine support is available and commonly used for async patterns.
- Secret values (12.0.0) — Some API returns are now opaque "secret" values that cannot be inspected, compared, or used in arithmetic. See the
wow-api-important instructions for full details.
- Instance restrictions (12.0.0) —
SendAddonMessage() is blocked in instances. Design addons to work without inter-player communication during instanced content.
Converted and distributed by TomeVault — claim your Tome and manage your conversions.
1---2name: wow-api-lua-environment3description: Complete reference for the WoW Lua 5.1 runtime environment, restrictions, secure execution, taint system, addon security model, timers, hooks, frame scripting, logging, and restricted actions. Covers hooksecurefunc, C_Timer, securecallfunction, issecurevariable, taint propagation, combat lockdown, protected frames, InCombatLockdown, C_RestrictedActions, C_Log, and the FrameScript sandbox. Use when working with Lua restrictions, secure code, taint, timers, hooks, addon security, debugging, or the WoW Lua sandbox. Use when this capability is needed.4---56# Lua Environment & Security (Retail — Patch 12.0.0)78Comprehensive reference for the WoW Lua sandbox, security model, taint system, secure execution, timers, hooks, logging, and restricted actions.910> **Source:** https://warcraft.wiki.gg/wiki/World_of_Warcraft_API11> **Secure Execution:** https://warcraft.wiki.gg/wiki/Secure_Execution_and_Tainting12> **Lua Functions:** https://warcraft.wiki.gg/wiki/Lua_functions13> **Current as of:** Patch 12.0.0 (Build 65655) — January 28, 202614> **Scope:** Retail only.1516## Scope1718This skill covers:1920- **Lua Sandbox** — WoW's Lua 5.1 environment, restricted standard library, blocked functions21- **Taint System** — How addon code becomes tainted and what tainted code cannot do22- **Secure Execution** — Protected functions, secure frames, secure handlers23- **Combat Lockdown** — What addons can and cannot do during combat24- **C_Timer** — Timer functions (After, NewTicker, NewTimer)25- **Hooks** — hooksecurefunc, securecallfunction, securecallmethod26- **C_RestrictedActions** — Addon restriction state queries27- **C_Log** — Logging utilities28- **FrameScript** — Frame script environment, secret values, scrubbing29- **Debugging** — Error handling, stack traces, debugging utilities3031## When to Use This Skill3233Use this skill when you need to:34- Understand what Lua functions are available vs blocked in WoW35- Work with or debug taint issues36- Write code that interacts with secure/protected frames37- Use timers, delayed execution, or ticker patterns38- Hook existing functions safely39- Understand combat lockdown restrictions40- Handle addon restriction states (12.0.0 instance restrictions)41- Log messages for debugging42- Work with secret values and the FrameScript sandbox4344---4546## WoW Lua 5.1 Sandbox4748WoW runs **Lua 5.1.4** with significant modifications. The following standard library functions are **blocked or removed**:4950### Blocked Standard Functions5152| Blocked | Reason |53|---------|--------|54| `loadfile()` | No filesystem access |55| `dofile()` | No filesystem access |56| `io.*` | No filesystem access |57| `os.execute()` | No shell access |58| `os.exit()` | Cannot close client |59| `os.remove()` | No filesystem |60| `os.rename()` | No filesystem |61| `os.tmpname()` | No filesystem |62| `os.getenv()` | No environment access |63| `package.*` | No package system |64| `require()` | No module loading |65| `module()` | No module system |66| `newproxy()` | Removed |67| `getfenv()` | Limited — returns read-only |68| `setfenv()` | Very restricted |69| `collectgarbage()` | Limited modes |7071### Available Standard Functions7273Most core Lua functions work normally:74- All `string.*`, `table.*`, `math.*` functions75- `type()`, `tostring()`, `tonumber()`, `rawget()`, `rawset()`, `rawequal()`, `rawlen()`76- `pairs()`, `ipairs()`, `next()`, `select()`, `unpack()`77- `pcall()`, `xpcall()`, `error()`, `assert()`78- `setmetatable()`, `getmetatable()`79- `coroutine.*` (full coroutine support)80- `os.time()`, `os.date()`, `os.clock()`, `os.difftime()`81- `print()` — outputs to default chat frame8283### WoW-Added Global Functions8485| Function | Description |86|----------|-------------|87| `strsplit(delimiter, str [, pieces])` | Split string by delimiter |88| `strsplittable(delimiter, str [, pieces])` | Split to table |89| `strjoin(delimiter, ...)` | Join strings |90| `strtrim(str [, chars])` | Trim whitespace |91| `tContains(table, value)` | Table contains value? |92| `tInsert(table, value)` | Insert into table (alias) |93| `tDeleteItem(table, value)` | Remove first occurrence of value |94| `tInvert(table)` | Invert key/value pairs |95| `wipe(table)` | Clear table (preserving reference) |96| `CopyTable(table [, shallow])` | Deep or shallow copy |97| `MergeTable(dest, source)` | Merge source into dest |98| `Mixin(object, ...)` | Copy mixin methods to object |99| `CreateFromMixins(...)` | Create new object from mixins |100| `CreateAndInitFromMixin(mixin, ...)` | Create + call Init |101| `format(formatString, ...)` | Alias for string.format |102| `tostringall(...)` | Convert all args to strings |103| `DevTools_Dump(value, startKey)` | Dump value for debugging |104105---106107## Taint System108109All addon code runs as **"tainted"** (insecure). Blizzard UI code runs as **"secure"** (untainted). The taint system prevents addons from calling protected functions or modifying secure frames.110111### How Taint Works1121131. Any variable set by addon code becomes **tainted**1142. Tainted values **propagate** — if tainted data flows into Blizzard code, it taints that path1153. Protected functions check taint before executing — they fail if execution path is tainted1164. Secure frames inherit security from their creation context117118### Checking Taint119120```lua121-- Check if a global variable is tainted122local isTainted, source = issecurevariable("SomeGlobalVar")123-- isTainted: false = secure, true = tainted124-- source: string name of the addon that tainted it (or nil if secure)125126-- Check table field127local isTainted, source = issecurevariable(someTable, "someKey")128```129130### Common Taint Pitfalls131132```lua133-- WRONG — This taints the Blizzard settings table134Settings.RegisterAddOnCategory = myFunc -- TAINT!135136-- WRONG — Modifying secure frame in insecure context137local btn = PlayerFrame -- This is a secure Blizzard frame138btn:SetAttribute("type", "spell") -- TAINT — can cause action blocked errors139140-- RIGHT — Use hooksecurefunc for observation without tainting141hooksecurefunc("SomeBlizzardFunction", function(...)142 -- Your code runs AFTER the original — doesn't taint143end)144```145146---147148## Secure Execution & Protected Functions149150### Protected Function Restrictions151152Functions marked `#protected` can only be called from:153- Secure (Blizzard) code154- Secure click handlers triggered by hardware events155- Inside `SecureActionButtonTemplate` handlers156157Protected functions include:158- All combat-related casting: `CastSpellByName()`, `CastSpellByID()`, `UseAction()`159- Item use: `UseItemByName()`, `UseContainerItem()` (in combat)160- Target changes: `TargetUnit()`, `AssistUnit()`, `FocusUnit()`161- Movement: `MoveForwardStart()`, `JumpOrAscendStart()`162- UI state: `SetAttribute()` on secure frames (in combat)163164### Combat Lockdown165166```lua167-- Check if in combat lockdown168if InCombatLockdown() then169 -- Cannot: create/destroy secure frames, change secure attributes170 -- Cannot: set points on secure frames, change parent/visibility of secure frames171 -- Can: read attributes, modify non-secure frames, queue changes for later172 return173end174175-- Queue changes for after combat176local frame = CreateFrame("Frame")177frame:RegisterEvent("PLAYER_REGEN_ENABLED")178frame:SetScript("OnEvent", function()179 -- Combat ended — safe to modify secure frames now180 DoSecureFrameChanges()181end)182```183184### Secure Handlers & Templates185186```lua187-- SecureActionButtonTemplate — allows protected actions via user clicks188local btn = CreateFrame("Button", "MySecureBtn", UIParent, "SecureActionButtonTemplate")189btn:SetAttribute("type", "spell")190btn:SetAttribute("spell", "Fireball")191-- When clicked by hardware event, this will cast Fireball192193-- SecureHandlerBaseTemplate — run secure snippets194local frame = CreateFrame("Frame", nil, UIParent, "SecureHandlerBaseTemplate")195frame:SetAttribute("_onstate-combat", [[196 -- This snippet runs in the secure environment197 if newstate == "combat" then198 self:Hide()199 else200 self:Show()201 end202]])203RegisterStateDriver(frame, "combat", "[combat] combat; nocombat")204```205206### State Drivers207208```lua209-- Register a state driver for automatic secure attribute updates210RegisterStateDriver(frame, "stateName", "conditionalString")211-- e.g., RegisterStateDriver(frame, "visibility", "[combat] hide; show")212213UnregisterStateDriver(frame, "stateName")214```215216---217218## C_Timer — Timer API219220> **Wiki:** https://warcraft.wiki.gg/wiki/API_C_Timer.After221222### Timer Functions223224| Function | Returns | Description |225|----------|---------|-------------|226| `C_Timer.After(seconds, callback)` | — | One-shot timer |227| `C_Timer.NewTimer(seconds, callback)` | `timer` | Cancellable one-shot timer |228| `C_Timer.NewTicker(seconds, callback [, iterations])` | `ticker` | Repeating timer |229230### Timer Object Methods231232```lua233local timer = C_Timer.NewTimer(5, function()234 print("5 seconds elapsed")235end)236timer:Cancel() -- Cancel before it fires237238local ticker = C_Timer.NewTicker(1, function()239 print("Every second")240end, 10) -- Stop after 10 iterations241ticker:Cancel() -- Or cancel early242243-- Simple delay (non-cancellable)244C_Timer.After(2, function()245 print("2 seconds later")246end)247```248249---250251## Hooks — Function Hooking252253### hooksecurefunc254255The primary safe hooking mechanism. Your hook runs **after** the original function, without tainting it.256257```lua258-- Hook a global function259hooksecurefunc("UseAction", function(slot, checkCursor, onSelf)260 print("Action used:", slot)261end)262263-- Hook a method on an object264hooksecurefunc(GameTooltip, "SetUnitAura", function(self, ...)265 -- Runs after GameTooltip:SetUnitAura266end)267268-- IMPORTANT: You CANNOT prevent the original from executing269-- IMPORTANT: You CANNOT modify the return values270-- IMPORTANT: Your hook does NOT taint the original function271```272273### securecallfunction / securecallmethod274275```lua276-- Call a function in secure context (if possible)277securecallfunction(func, arg1, arg2)278279-- Call a method in secure context280securecallmethod(object, "MethodName", arg1, arg2)281```282283---284285## C_RestrictedActions — Addon Restriction State286287New in 12.0.0. Tracks when addon restrictions are active (e.g., inside instances).288289| Function | Returns | Description |290|----------|---------|-------------|291| `C_RestrictedActions.GetAddOnRestrictionState(type)` | `state` | Current restriction state |292| `C_RestrictedActions.IsAddOnRestrictionActive(type)` | `active` | Is restriction currently active? |293| `C_RestrictedActions.CheckAllowProtectedFunctions(object [, silent])` | `protectedFunctionsAllowed` | Can object call protected funcs? |294| `InCombatLockdown()` | `inCombatLockdown` | Combat lockdown active? |295296### Restriction Events297298| Event | Description |299|-------|-------------|300| `ADDON_RESTRICTION_STATE_CHANGED` | Restriction state changed (entering/leaving instance) |301| `PLAYER_REGEN_DISABLED` | Entering combat |302| `PLAYER_REGEN_ENABLED` | Leaving combat |303304---305306## C_Log — Logging307308| Function | Description |309|----------|-------------|310| `C_Log.LogMessage(message)` | Log info message |311| `C_Log.LogWarningMessage(message)` | Log warning |312| `C_Log.LogErrorMessage(message)` | Log error |313| `C_Log.LogMessageWithPriority(priority, message)` | Log with specific priority |314315> **Note:** `ConsolePrint()` was removed in 12.0.0. Use `C_Log.LogMessage()` instead.316317---318319## FrameScript Functions320321WoW provides special FrameScript functions for working with the secure/secret value system:322323| Function | Returns | Description |324|----------|---------|-------------|325| `issecurevariable([table,] name)` | `isSecure, taintSource` | Check taint status |326| `issecretvalue(value)` | `isSecret` | Is value a secret? |327| `issecrettable(table)` | `isSecretOrContentsSecret` | Is table or contents secret? |328| `canaccessvalue(value)` | `isAccessible` | Can addon access this value? |329| `hasanysecretvalues(values)` | `isAnyValueSecret` | Any arg secret? |330| `scrubsecretvalues(values)` | `scrubbed` | Replace secrets with nil |331| `secretwrap(values)` | `wrapped` | Wrap values as secrets |332| `mapvalues(func, values)` | `mapped` | Map function over values (secret-safe) |333| `securecallfunction(func, ...)` | `results` | Call in secure context |334| `securecallmethod(obj, method, ...)` | `results` | Call method in secure context |335| `forceinsecure()` | — | Force insecure execution |336| `seterrorhandler(handler)` | — | Set global error handler |337| `geterrorhandler()` | `handler` | Get current error handler |338339---340341## Debugging Utilities342343### Error Handling344345```lua346-- Set a custom error handler347seterrorhandler(function(msg)348 -- msg is the error string349 print("ERROR:", msg)350end)351352-- Protected call with error handling353local success, err = pcall(function()354 -- Code that might error355end)356if not success then357 print("Error:", err)358end359360-- xpcall with message handler361local success, err = xpcall(function()362 error("something broke")363end, function(msg)364 return msg .. "\n" .. debugstack(2)365end)366```367368### Debug Stack & Info369370```lua371-- Get a stack trace372local stack = debugstack([thread,] [start [, count1 [, count2]]])373374-- Get debug info375local info = debuglocals([thread,] [level])376377-- Profile timing378debugprofilestart()379-- ... code to measure ...380local elapsed = debugprofilestop() -- microseconds381```382383### Slash Commands for Debugging384385```lua386-- /dump expression — evaluates and prints387-- /run code — executes Lua code388-- /script code — same as /run389-- /console cvarName [value] — get/set console variables390```391392---393394## Common Patterns395396### Deferred Initialization (Wait for Login)397398```lua399local frame = CreateFrame("Frame")400frame:RegisterEvent("PLAYER_LOGIN")401frame:SetScript("OnEvent", function(self, event)402 -- Safe to initialize — player is logged in403 self:UnregisterEvent(event)404 InitializeAddon()405end)406```407408### Safe OnUpdate Throttle409410```lua411local elapsed = 0412local THROTTLE = 0.1 -- 100ms413frame:SetScript("OnUpdate", function(self, dt)414 elapsed = elapsed + dt415 if elapsed < THROTTLE then return end416 elapsed = 0417 -- Do periodic work418end)419```420421### Post-Combat Action Queue422423```lua424local pendingActions = {}425426local function QueueAction(action)427 if InCombatLockdown() then428 tinsert(pendingActions, action)429 else430 action()431 end432end433434local frame = CreateFrame("Frame")435frame:RegisterEvent("PLAYER_REGEN_ENABLED")436frame:SetScript("OnEvent", function()437 for _, action in ipairs(pendingActions) do438 action()439 end440 wipe(pendingActions)441end)442```443444### Graceful Secret Value Handling (12.0.0)445446```lua447-- When values might be secret, pass them directly to widgets448local name = UnitName(unit) -- may be secret449myFontString:SetText(name) -- widgets accept secrets450451-- Check if a value is secret before trying operations452if not issecretvalue(someValue) then453 -- Safe to compare, do arithmetic, etc.454 if someValue == "expected" then ... end455else456 -- Cannot inspect — pass to UI widget directly457 myWidget:SetText(someValue)458end459```460461---462463## Gotchas & Restrictions4644651. **No `require()`** — WoW has no module system. Use the TOC file to control load order. Libraries are embedded directly.4662. **`setfenv()` / `getfenv()`** — Severely restricted. Do not rely on environment manipulation.4673. **`collectgarbage()`** — Only `"count"` mode works. Cannot force GC collection.4684. **Taint is sticky** — Once a variable is tainted, it stays tainted. Even if you set it back to the original value, the taint remains.4695. **`print()` goes to chat** — Unlike standard Lua, `print()` outputs to the default chat frame, not stdout.4706. **String library additions** — WoW adds `strsplit`, `strjoin`, `strtrim`, and `strmatch` as globals (in addition to `string.match`).4717. **No `os.exit()`** — Cannot terminate the client programmatically.4728. **Coroutines work** — Full coroutine support is available and commonly used for async patterns.4739. **Secret values (12.0.0)** — Some API returns are now opaque "secret" values that cannot be inspected, compared, or used in arithmetic. See the `wow-api-important` instructions for full details.47410. **Instance restrictions (12.0.0)** — `SendAddonMessage()` is blocked in instances. Design addons to work without inter-player communication during instanced content.475476---477> Converted and distributed by [TomeVault](https://tomevault.io/claim/jburlison) — claim your Tome and manage your conversions.478<!-- tomevault:4.0:skill_md:2026-04-13 -->