Lua API & C_ Namespaces¶
WoW's addon API is extensive — 260+ C_ namespaces plus hundreds of global functions. This page covers the essentials you'll use in most addons.
Canonical reference: warcraft.wiki.gg/wiki/World_of_Warcraft_API
Lua Sandbox¶
WoW embeds Lua 5.1 in a sandboxed environment. Not all standard libraries are available.
Available Libraries¶
| Library | Notes |
|---|---|
| Base functions | pairs, ipairs, next, select, type, tostring, tonumber, unpack, pcall, xpcall, error, assert, rawget, rawset, rawequal, setmetatable, getmetatable |
| string | All standard functions (string.find, string.gsub, string.format, etc.) |
| table | table.insert, table.remove, table.sort, table.concat, table.getn |
| math | All standard functions — trig functions use degrees, not radians |
| bit | bit.band, bit.bor, bit.bxor, bit.bnot, bit.lshift, bit.rshift |
| coroutine | Full coroutine support |
Blocked / Restricted¶
| Item | Status |
|---|---|
os, io, debug | Removed entirely — no filesystem or OS access |
dofile, require | Removed — use TOC file loading order instead |
loadstring | Returns tainted code — the resulting function is insecure and cannot call protected APIs |
setfenv / getfenv | Taint risk — using these on a function marks it (and its callers) as tainted |
WoW Global Functions¶
These are the most commonly used global functions provided by the WoW client.
Frame Creation¶
| Function | Description |
|---|---|
CreateFrame(type [, name, parent, template, id]) | Create a UI frame. type is a string like "Frame", "Button", "EditBox". Returns the new frame. |
-- Create a simple frame
local frame = CreateFrame("Frame", "MyAddonFrame", UIParent, "BackdropTemplate")
frame:SetSize(200, 100)
frame:SetPoint("CENTER")
-- Create a button from a template
local btn = CreateFrame("Button", nil, frame, "UIPanelButtonTemplate")
btn:SetSize(120, 25)
btn:SetText("Click Me")
btn:SetPoint("CENTER")
btn:SetScript("OnClick", function(self)
print("Button clicked!")
end)
Time¶
| Function | Description |
|---|---|
GetTime() | Returns game time in seconds as a float (high precision) |
time() | Returns server time as a Unix timestamp (integer) |
date(fmt [, time]) | Format a time value (like os.date in standard Lua) |
Output¶
| Function | Description |
|---|---|
print(...) | Print to the default chat frame. Concatenates args with spaces. |
format(fmt, ...) | Alias for string.format. Format a string with substitutions. |
String Utilities¶
| Function | Description |
|---|---|
strsplit(delimiter, str [, pieces]) | Split a string. Returns multiple values (not a table). |
strjoin(delimiter, ...) | Join values with a delimiter. |
strtrim(str [, chars]) | Trim whitespace (or specified chars) from both ends. |
strmatch(str, pattern) | Alias for string.match. |
strfind(str, pattern [, init [, plain]]) | Alias for string.find. |
-- strsplit returns multiple values, not a table
local cmd, arg1, rest = strsplit(" ", msg, 3)
-- strjoin
local path = strjoin("/", "Interface", "AddOns", addonName)
-- strtrim
local cleaned = strtrim(" hello ") -- "hello"
Table Utilities¶
| Function | Description |
|---|---|
tinsert(t, [pos,] value) | Alias for table.insert |
tremove(t [, pos]) | Alias for table.remove |
wipe(t) | Remove all keys from a table (reuses memory — faster than t = {}) |
tContains(t, value) | Returns true if value is in array t |
tInvert(t) | Returns a new table with keys and values swapped |
CopyTable(t) | Deep copy a table (recursively copies subtables) |
-- wipe reuses the table allocation
local cache = {}
-- ... fill cache ...
wipe(cache) -- clears it without creating garbage
-- tContains for membership checks
local validTypes = {"warrior", "mage", "priest"}
if tContains(validTypes, playerClass) then
-- handle
end
-- tInvert for reverse lookups
local lookup = tInvert(validTypes)
-- lookup = { warrior = 1, mage = 2, priest = 3 }
-- CopyTable for safe defaults
local db = CopyTable(defaults)
OOP Utilities¶
| Function | Description |
|---|---|
Mixin(target, ...) | Copy all fields from mixin tables into target. Returns target. |
CreateFromMixins(...) | Create a new table with fields from all provided mixins. |
-- Define a mixin
local HealthBarMixin = {}
function HealthBarMixin:Init(unit)
self.unit = unit
self:Update()
end
function HealthBarMixin:Update()
local hp = UnitHealth(self.unit)
local max = UnitHealthMax(self.unit)
self.bar:SetValue(hp / max)
end
-- Create an instance
local healthBar = CreateFromMixins(HealthBarMixin)
healthBar.bar = CreateFrame("StatusBar", nil, UIParent)
healthBar:Init("player")
-- Or mixin into an existing frame
local frame = CreateFrame("Frame")
Mixin(frame, HealthBarMixin)
frame.bar = frame:CreateTexture()
frame:Init("target")
Hooking & Security¶
| Function | Description |
|---|---|
hooksecurefunc([table,] "name", hook) | Post-hook a function without causing taint. Hook runs after the original. |
securecall(func, ...) | Call a function in a secure context (rarely needed by addons). |
issecurevariable([table,] "name") | Check if a variable is secure (untainted). Returns isSecure, taintedBy. |
InCombatLockdown() | Returns true if the player is in combat lockdown (protected actions blocked). |
Debug¶
| Function | Description |
|---|---|
debugstack([thread,] [start [, count1 [, count2]]]) | Returns a stack trace string. Useful for debugging. |
debugprofilestart() | Start a high-precision timer. |
debugprofilestop() | Returns milliseconds elapsed since debugprofilestart(). |
-- Profile a function
debugprofilestart()
MyExpensiveFunction()
local elapsed = debugprofilestop()
print(format("Took %.2f ms", elapsed))
C_ Namespace APIs¶
WoW's modern API is organized into 260+ C_ namespaces. These are the ones you'll use most often.
C_Timer¶
Non-blocking timers. Always prefer these over OnUpdate for simple delays.
| Function | Description |
|---|---|
C_Timer.After(seconds, callback) | One-shot timer. No cancel handle. |
C_Timer.NewTimer(seconds, callback) | One-shot timer. Returns a handle with :Cancel(). |
C_Timer.NewTicker(seconds, callback [, iterations]) | Repeating timer. Returns a handle with :Cancel(). |
-- Simple delay
C_Timer.After(2, function()
print("2 seconds later!")
end)
-- Cancellable one-shot
local timer = C_Timer.NewTimer(5, function()
print("This may never fire")
end)
timer:Cancel() -- cancel before it fires
-- Repeating ticker
local ticker = C_Timer.NewTicker(1, function()
print("Every second")
end)
-- Stop it later
C_Timer.After(10, function()
ticker:Cancel()
end)
-- Limited iterations (fires 5 times then stops)
C_Timer.NewTicker(0.5, function(self)
print("Tick", self._remainingIterations)
end, 5)
C_ChatInfo — Addon Communication¶
Send messages between addon instances across the network.
| Function | Description |
|---|---|
C_ChatInfo.RegisterAddonMessagePrefix(prefix) | Register a prefix (max 16 chars). Must register before receiving. |
C_ChatInfo.SendAddonMessage(prefix, message, chatType [, target]) | Send a message. chatType: "PARTY", "RAID", "GUILD", "WHISPER". |
Limits: Max 255 bytes per message. Throttled to ~10 messages/second. Exceeding the throttle queues messages.
local PREFIX = "MyAddon"
C_ChatInfo.RegisterAddonMessagePrefix(PREFIX)
-- Send a message to the raid
C_ChatInfo.SendAddonMessage(PREFIX, "PULL:10", "RAID")
-- Receive messages via event
function eventHandlers:CHAT_MSG_ADDON(prefix, message, channel, sender)
if prefix ~= PREFIX then return end
local cmd, data = strsplit(":", message, 2)
if cmd == "PULL" then
local countdown = tonumber(data)
print(sender .. " is pulling in " .. countdown .. " seconds!")
end
end
C_Map¶
Map and zone information.
| Function | Description |
|---|---|
C_Map.GetBestMapForUnit(unitToken) | Returns the most specific mapID for a unit's location. |
C_Map.GetMapInfo(mapID) | Returns map info table: { mapID, name, mapType, parentMapID }. |
C_Map.GetPlayerMapPosition(mapID, unitToken) | Returns a Vector2D with :GetXY() for player position (0–1 range). |
C_Map.GetAreaInfo(areaID) | Returns the name of a map area. |
local mapID = C_Map.GetBestMapForUnit("player")
if mapID then
local info = C_Map.GetMapInfo(mapID)
print("Current zone:", info.name)
local pos = C_Map.GetPlayerMapPosition(mapID, "player")
if pos then
local x, y = pos:GetXY()
print(format("Position: %.1f%%, %.1f%%", x * 100, y * 100))
end
end
C_Item¶
Item data queries. Many functions are async — data may not be available immediately.
| Function | Description |
|---|---|
C_Item.GetItemInfo(itemID) | Returns item info (name, link, quality, etc.). May return nil if not cached. |
C_Item.GetItemNameByID(itemID) | Returns item name. May require loading. |
C_Item.GetItemIconByID(itemID) | Returns the icon texture path/ID. |
C_Item.RequestLoadItemDataByID(itemID) | Request the server to load item data. Listen for ITEM_DATA_LOAD_RESULT. |
C_Item.IsItemDataCachedByID(itemID) | Check if item data is already cached locally. |
-- Safe item info retrieval with async fallback
local function GetItemInfoAsync(itemID, callback)
if C_Item.IsItemDataCachedByID(itemID) then
callback(C_Item.GetItemInfo(itemID))
return
end
-- Request load and wait for event
local item = Item:CreateFromItemID(itemID)
item:ContinueOnItemLoad(function()
callback(C_Item.GetItemInfo(itemID))
end)
end
C_Spell¶
Spell information queries.
| Function | Description |
|---|---|
C_Spell.GetSpellInfo(spellID) | Returns spell info table with name, iconID, castTime, etc. |
C_Spell.GetSpellName(spellID) | Returns the localized spell name. |
C_Spell.GetSpellCooldown(spellID) | Returns cooldown info: startTime, duration, isEnabled, modRate. |
C_Spell.GetSpellTexture(spellID) | Returns the spell's icon texture. |
local info = C_Spell.GetSpellInfo(12345)
if info then
print(info.name, "cast time:", info.castTime / 1000, "sec")
end
C_AddOns¶
Addon management functions.
| Function | Description |
|---|---|
C_AddOns.GetNumAddOns() | Returns the total number of addons. |
C_AddOns.GetAddOnInfo(index) | Returns name, title, notes, loadable, reason, security, newVersion. |
C_AddOns.IsAddOnLoaded(indexOrName) | Returns loaded, finished. |
C_AddOns.GetAddOnMetadata(addon, field) | Read a TOC field value (e.g., "Version", "X-Category"). |
C_AddOns.LoadAddOn(indexOrName) | Load a LoadOnDemand addon at runtime. |
-- Check if another addon is loaded
local loaded = C_AddOns.IsAddOnLoaded("Details")
if loaded then
print("Details is loaded, enabling integration")
end
-- Read your own version from TOC
local version = C_AddOns.GetAddOnMetadata(addonName, "Version")
print(addonName, "v" .. version)
C_Container¶
Bag and inventory operations.
| Function | Description |
|---|---|
C_Container.GetContainerNumSlots(bagID) | Number of slots in a bag (0 = backpack, 1–4 = bags). |
C_Container.GetContainerItemInfo(bagID, slot) | Returns item info table for a bag slot. |
C_Container.GetContainerItemID(bagID, slot) | Returns the itemID in a bag slot. |
C_Container.GetContainerNumFreeSlots(bagID) | Returns free slots and bag type. |
C_Container.UseContainerItem(bagID, slot) | Use/equip an item (protected in combat). |
-- Count free bag slots
local freeSlots = 0
for bag = 0, 4 do
local free = C_Container.GetContainerNumFreeSlots(bag)
freeSlots = freeSlots + free
end
print("Free bag slots:", freeSlots)
-- Find an item in bags
local function FindItemInBags(targetItemID)
for bag = 0, 4 do
for slot = 1, C_Container.GetContainerNumSlots(bag) do
local itemID = C_Container.GetContainerItemID(bag, slot)
if itemID == targetItemID then
return bag, slot
end
end
end
return nil
end
C_UnitAuras¶
Buff and debuff queries (modern replacement for UnitAura).
| Function | Description |
|---|---|
C_UnitAuras.GetAuraDataByIndex(unit, index [, filter]) | Get aura data by index. Returns aura info table or nil. |
C_UnitAuras.GetAuraDataByAuraInstanceID(unit, auraInstanceID) | Get aura data by its unique instance ID (preferred). |
C_UnitAuras.GetAuraDataBySpellName(unit, spellName [, filter]) | Get aura data by spell name. |
C_UnitAuras.GetPlayerAuraBySpellID(spellID) | Shortcut for checking player buffs/debuffs. |
Filters: "HELPFUL" (buffs), "HARMFUL" (debuffs), "PLAYER" (cast by player), "CANCELABLE". Combine with space: "HARMFUL|PLAYER".
-- Check if player has a specific buff
local aura = C_UnitAuras.GetPlayerAuraBySpellID(1459) -- Arcane Intellect
if aura then
print("Arcane Intellect active, expires in", aura.expirationTime - GetTime(), "sec")
end
-- Iterate all debuffs on target
local i = 1
while true do
local aura = C_UnitAuras.GetAuraDataByIndex("target", i, "HARMFUL")
if not aura then break end
print(aura.name, "stacks:", aura.applications, "from:", aura.sourceUnit or "unknown")
i = i + 1
end
C_EncodingUtil¶
Data encoding and compression utilities (added in 11.1.5).
| Function | Description |
|---|---|
C_EncodingUtil.MakeHardenedTable(t) | Creates a tamper-resistant table. |
Note: This namespace is relatively new. Check the wiki for the latest available functions.
Slash Command Registration¶
Register custom slash commands for your addon.
local addonName, ns = ...
-- Register one or more slash command aliases
SLASH_MYADDON1 = "/myaddon"
SLASH_MYADDON2 = "/ma"
SlashCmdList["MYADDON"] = function(msg)
local cmd, rest = strsplit(" ", msg, 2)
cmd = (cmd or ""):lower()
if cmd == "config" or cmd == "options" then
ns:OpenConfig()
elseif cmd == "reset" then
ns:ResetPosition()
print("|cff00ff00MyAddon:|r Position reset.")
elseif cmd == "toggle" then
ns.db.enabled = not ns.db.enabled
print("|cff00ff00MyAddon:|r " .. (ns.db.enabled and "Enabled" or "Disabled"))
elseif cmd == "debug" then
ns.db.debug = not ns.db.debug
print("|cff00ff00MyAddon:|r Debug " .. (ns.db.debug and "ON" or "OFF"))
else
print("|cff00ff00MyAddon|r commands:")
print(" /ma config — Open settings")
print(" /ma toggle — Enable/disable")
print(" /ma reset — Reset position")
print(" /ma debug — Toggle debug mode")
end
end
Rules: - The global name must be SLASH_COMMANDNAME1 (the number is the alias index) - The key in SlashCmdList must match the COMMANDNAME part (without SLASH_ or the number) - Slash commands are case-insensitive by the game client - The msg argument is everything after the slash command itself
hooksecurefunc Pattern¶
Post-hook any function without causing taint. The hook runs after the original function.
-- Hook a global function
hooksecurefunc("TargetFrame_Update", function(self)
-- Runs after every TargetFrame_Update call
-- self is the same first argument passed to the original
print("Target frame updated")
end)
-- Hook a method on a specific object
hooksecurefunc(GameTooltip, "SetUnitBuff", function(self, unit, index)
-- Runs after GameTooltip:SetUnitBuff()
-- Add extra info to tooltip
self:AddLine("Custom addon info here", 0.5, 0.8, 1.0)
self:Show() -- refresh tooltip height
end)
-- Hook ChatFrame to intercept messages
hooksecurefunc(ChatFrame1, "AddMessage", function(self, text, ...)
-- Runs after every chat message is added
if text and text:find("pattern") then
-- do something
end
end)
Limitations:
- Cannot prevent the original function from running
- Cannot modify the return value of the original
- Cannot unhook — once hooked, it's hooked for the session
- Cannot change the arguments the original receives
- The hook itself is tainted (addon code), but the original remains secure
Addon Communication¶
Send and receive messages between addon instances on different clients.
Setup¶
local addonName, ns = ...
local PREFIX = "MyAddon" -- max 16 characters
-- Must register before you can receive
C_ChatInfo.RegisterAddonMessagePrefix(PREFIX)
Sending¶
-- chatType: "PARTY", "RAID", "GUILD", "WHISPER", "CHANNEL"
C_ChatInfo.SendAddonMessage(PREFIX, "HELLO:1.0", "PARTY")
-- Whisper to a specific player
C_ChatInfo.SendAddonMessage(PREFIX, "VERSION:1.0", "WHISPER", "PlayerName")
-- Channel (must know the channel number)
C_ChatInfo.SendAddonMessage(PREFIX, data, "CHANNEL", channelNumber)
Receiving¶
function eventHandlers:CHAT_MSG_ADDON(prefix, message, channel, sender)
if prefix ~= PREFIX then return end
-- Ignore messages from self
local playerName = UnitName("player")
if sender == playerName or sender:find(playerName) then return end
local cmd, data = strsplit(":", message, 2)
if cmd == "HELLO" then
-- Respond with our version
C_ChatInfo.SendAddonMessage(PREFIX, "HELLO:" .. ns.version, channel)
end
end
Limits & Best Practices¶
| Constraint | Value |
|---|---|
| Max prefix length | 16 characters |
| Max message length | 255 bytes |
| Send throttle | ~10 messages/second |
| Max registered prefixes (all addons) | 128 |
- Serialize long data by splitting across multiple messages with sequence numbers
- Throttle outgoing messages — exceeding the limit queues them (causes delay, not errors)
- Use compact encodings — every byte counts in the 255-byte limit
- Prefix with addon version so you can handle protocol changes
Useful Utility Patterns¶
Throttle Function¶
Prevent a function from running more than once per interval.
local function CreateThrottle(interval)
local lastCall = 0
return function(func, ...)
local now = GetTime()
if now - lastCall >= interval then
lastCall = now
return func(...)
end
end
end
-- Usage
local throttledUpdate = CreateThrottle(0.1) -- max 10/sec
frame:SetScript("OnUpdate", function(self, elapsed)
throttledUpdate(function()
-- expensive work here
end)
end)
Debounce Function¶
Delay execution until calls stop arriving for a specified duration.
local function CreateDebounce(delay)
local timer = nil
return function(func)
if timer then
timer:Cancel()
end
timer = C_Timer.NewTimer(delay, function()
timer = nil
func()
end)
end
end
-- Usage: only update after user stops typing for 0.3s
local debouncedSearch = CreateDebounce(0.3)
editBox:SetScript("OnTextChanged", function(self)
local text = self:GetText()
debouncedSearch(function()
ns:PerformSearch(text)
end)
end)
Deep Table Comparison¶
local function DeepEqual(a, b)
if type(a) ~= type(b) then return false end
if type(a) ~= "table" then return a == b end
for k, v in pairs(a) do
if not DeepEqual(v, b[k]) then return false end
end
for k in pairs(b) do
if a[k] == nil then return false end
end
return true
end
Safe Callback Queue (Combat-Aware)¶
local pendingActions = {}
local function ExecuteOrQueue(action)
if InCombatLockdown() then
table.insert(pendingActions, action)
else
action()
end
end
function eventHandlers:PLAYER_REGEN_ENABLED()
for _, action in ipairs(pendingActions) do
action()
end
wipe(pendingActions)
end
-- Usage
ExecuteOrQueue(function()
-- This involves protected operations (e.g., SetAttribute)
mySecureButton:SetAttribute("macrotext", "/cast Fireball")
end)
Colored Print Helper¶
local function AddonPrint(...)
local prefix = "|cff00ccff[MyAddon]|r "
print(prefix, ...)
end
-- Color codes: |cffRRGGBB starts color, |r resets
-- Common colors:
-- |cffff0000 red |cff00ff00 green
-- |cff00ccff light blue |cffffff00 yellow
-- |cffff8800 orange |cffff00ff pink
Next Steps¶
- Event System — Deep dive into WoW's event-driven architecture
- Frame & Widget System — Building UI with frames, textures, and font strings
- Security Model — Understanding taint, combat lockdown, and protected functions
- warcraft.wiki.gg API Reference — Complete API listing