The AI-Assisted WoW Addon Developer's Guide¶
Everything AI needs to know to write correct Midnight (12.0+) addon code
The Problem
AI models hallucinate WoW API functions, use deprecated patterns from 2015, and have no idea that Midnight (12.0) rewrote the rules. GetSpellInfo() is dead. COMBAT_LOG_EVENT_UNFILTERED is gone. The addon paradigm has fundamentally shifted.
The Solution
This guide + our custom agents. Paste the System Prompt into any AI before asking it to write WoW code. Use the Deprecated Function Map to catch hallucinations. Follow the Golden Rules and your AI-generated code will actually work.
The Golden Rules¶
These are non-negotiable. Every piece of AI-generated WoW addon code must follow all ten.
Rule 1 — Interface: 120001
Always. No exceptions. This is WoW 12.0.1 Midnight. Your .toc file must contain:
Previous values like 110002, 100207, 110100 are wrong for Midnight.
Rule 2 — Lua 5.1
WoW's Lua is frozen in 2006. These do not exist:
- No
gotostatements - No bitwise operators (
&,|,~,<<,>>) - No
_ENV(usesetfenv/getfenvif you must) - No
load()with environment arg (useloadstring()) - No integer division
//
Use bit.band(), bit.bor(), bit.lshift() for bitwise operations.
Rule 3 — C_ Namespace Functions
GetSpellInfo() is DEAD. C_Spell.GetSpellInfo() is alive. Blizzard has been migrating all global functions into C_ namespaces since Dragonflight. If the AI writes a bare global function, check if a C_ version exists.
Rule 4 — Namespace Everything
Every .lua file must start with the addon namespace. No globals except SavedVariables.
Store all shared state on ns. Never write MyAddon_Data = {} in global scope.
Rule 5 — Events, Not Polling
Use the table dispatch pattern. Register for events. Never use OnUpdate for what an event can do.
local frame = CreateFrame("Frame")
local events = {}
function events:PLAYER_LOGIN()
print("Logged in!")
end
function events:ADDON_LOADED(loadedAddon)
if loadedAddon == addonName then
-- init SavedVariables here
end
end
frame:SetScript("OnEvent", function(self, event, ...)
if events[event] then
events[event](self, ...)
end
end)
for event in pairs(events) do
frame:RegisterEvent(event)
end
Rule 6 — Combat Lockdown
Always check InCombatLockdown() before modifying protected frames. Queue the operation and execute on PLAYER_REGEN_ENABLED.
Rule 7 — Skin, Don't Replace
Midnight's #1 design rule. Enhance Blizzard's frames, don't create custom replacements for standard UI elements. Hook into existing frames with hooksecurefunc().
-- WRONG: Replacing Blizzard's tooltip
local myTooltip = CreateFrame("GameTooltip", "MyCustomTooltip", UIParent)
-- CORRECT: Enhancing Blizzard's tooltip
GameTooltip:HookScript("OnTooltipSetItem", function(self)
local _, link = self:GetItem()
if link then
self:AddLine("My custom info here", 0.5, 0.8, 1.0)
end
end)
Rule 8 — No CLEU
COMBAT_LOG_EVENT_UNFILTERED is gone for addons in Midnight. Use specific unit events instead:
| Instead of CLEU for... | Use this event |
|---|---|
| Tracking damage/healing | UNIT_HEALTH, post-combat data |
| Tracking buffs/debuffs | UNIT_AURA + C_UnitAuras |
| Tracking casts | UNIT_SPELLCAST_START, _SUCCEEDED, _FAILED |
| Tracking deaths | UNIT_HEALTH reaching 0, PLAYER_DEAD |
| Combat summary | ENCOUNTER_END, PLAYER_REGEN_ENABLED |
Rule 9 — Verify Every Function
Before using any API function, verify it exists at:
https://warcraft.wiki.gg/wiki/API_FunctionName
If the wiki page says "Removed in patch 12.0" — it's dead. Find the replacement.
Rule 10 — Complete, Not Snippets
Always generate full .toc + .lua files, never code fragments. A snippet that works in isolation will fail when loaded as an addon because it's missing the namespace, event registration, or SavedVariables initialization.
The Quick Start¶
A minimal but correct Midnight addon template with every line explained:
-- Every file starts with the addon namespace
-- addonName = "MyAddon" (string), ns = shared namespace table
local addonName, ns = ...
-- Default settings (merged with SavedVariables on load)
ns.defaults = {
enabled = true,
scale = 1.0,
welcomeShown = false,
}
-- Event handler table — dispatch pattern, no if/elseif chains
local events = {}
-- SavedVariables are ONLY safe to read after ADDON_LOADED fires
function events:ADDON_LOADED(loadedAddon)
if loadedAddon ~= addonName then return end
-- Initialize SavedVariables with defaults
if not MyAddonDB then
MyAddonDB = {}
end
for key, value in pairs(ns.defaults) do
if MyAddonDB[key] == nil then
MyAddonDB[key] = value
end
end
ns.db = MyAddonDB
-- Unregister — this event only matters once for us
self:UnregisterEvent("ADDON_LOADED")
end
function events:PLAYER_LOGIN()
if ns.db and not ns.db.welcomeShown then
print("|cff00ccff[MyAddon]|r Welcome to Midnight! Type /myaddon for options.")
ns.db.welcomeShown = true
end
end
-- Create the event frame and wire up dispatch
local frame = CreateFrame("Frame")
frame:SetScript("OnEvent", function(self, event, ...)
if events[event] then
events[event](self, ...)
end
end)
for event in pairs(events) do
frame:RegisterEvent(event)
end
-- Slash command — always provide one for user interaction
SLASH_MYADDON1 = "/myaddon"
SlashCmdList["MYADDON"] = function(msg)
local cmd = msg:lower():trim()
if cmd == "reset" then
MyAddonDB = nil
ReloadUI()
else
print("|cff00ccff[MyAddon]|r Options:")
print(" /myaddon reset — Reset all settings")
end
end
The Deprecated Function Map¶
AI models hallucinate these constantly
The left column is what AI will write. The right column is what actually works in Midnight.
| Deprecated / Removed | Correct Replacement (12.0+) | Notes |
|---|---|---|
GetSpellInfo(id) | C_Spell.GetSpellInfo(id) | Returns a table (info.name, info.iconID), not multiple values |
GetSpellDescription(id) | C_Spell.GetSpellDescription(id) | Returns string |
GetSpellTexture(id) | C_Spell.GetSpellTexture(id) | Returns textureID |
GetSpellCooldown(id) | C_Spell.GetSpellCooldown(id) | Returns a table |
GetSpellCharges(id) | C_Spell.GetSpellCharges(id) | Returns a table |
IsSpellKnown(id) | C_Spell.IsSpellDataCached(id) | Behavior changed |
GetItemInfo(id) | C_Item.GetItemInfo(id) | Async — may return nil, use callback |
GetItemIcon(id) | C_Item.GetItemIconByID(id) | — |
GetContainerNumSlots(bag) | C_Container.GetContainerNumSlots(bag) | — |
GetContainerItemInfo(bag, slot) | C_Container.GetContainerItemInfo(bag, slot) | Returns a table |
GetContainerItemLink(bag, slot) | C_Container.GetContainerItemLink(bag, slot) | — |
PickupContainerItem(bag, slot) | C_Container.PickupContainerItem(bag, slot) | — |
UseContainerItem(bag, slot) | C_Container.UseContainerItem(bag, slot) | — |
GetAddOnInfo(index) | C_AddOns.GetAddOnInfo(index) | — |
GetNumAddOns() | C_AddOns.GetNumAddOns() | — |
IsAddOnLoaded(name) | C_AddOns.IsAddOnLoaded(name) | — |
GetSpecialization() | PlayerUtil.GetCurrentSpecID() | Entirely different pattern |
GetSpecializationInfo(id) | C_SpecializationInfo.GetSpecializationInfo(id) | — |
GetAchievementInfo(id) | C_AchievementInfo.GetAchievementInfo(id) | — |
GetCurrencyInfo(id) | C_CurrencyInfo.GetCurrencyInfo(id) | Returns a table |
UnitAura(unit, index) | C_UnitAuras.GetAuraDataByIndex(unit, index) | Returns AuraData table |
UnitBuff(unit, index) | C_UnitAuras.GetBuffDataByIndex(unit, index) | Returns AuraData table |
UnitDebuff(unit, index) | C_UnitAuras.GetDebuffDataByIndex(unit, index) | Returns AuraData table |
CombatLogGetCurrentEventInfo() | REMOVED — no replacement | CLEU is gone for addons in 12.0 |
GetTalentInfo(tier, col, group) | C_ClassTalents + talent tree APIs | Completely reworked system |
SendChatMessage() in instances | Restricted | Chat in instances is now opaque via Secret Values |
Pattern to remember
If a C_ version returns a table instead of multiple values, you must destructure it:
The Midnight Paradigm Shift¶
12.0 changed everything
Midnight (Patch 12.0) made the largest addon API changes in WoW's 20-year history. Blizzard calls it "Addon Disarmament." The community calls it the "Addon Apocalypse."
Secret Values — Combat numbers (damage, healing, absorbs) are now opaque SecretValue objects. Addons can display them via Blizzard's UI, but cannot read, compare, or store the raw numbers. DPS meters now work fundamentally differently.
CLEU Removed — COMBAT_LOG_EVENT_UNFILTERED, the backbone of every combat addon for 15 years, no longer fires for addon code. Use specific unit events (UNIT_HEALTH, UNIT_AURA, UNIT_SPELLCAST_*) instead.
Skin, Don't Replace — Blizzard explicitly encourages addons to enhance existing UI rather than replacing it. Custom action bars, unit frames, and nameplates must hook into Blizzard's secure frames.
Instance Communication Restricted — Addon channel messages in instances are limited. Chat messages become Secret Values in dungeons and raids.
The Official Statement: Combat Philosophy and Addon Disarmament in Midnight
Guide Map¶
-
API Cheat Sheet
Correct function signatures for every commonly-used API call, verified against 12.0.1.
-
Common Pitfalls
What AI gets wrong and how to fix it — the 20 most common hallucinations.
-
Code Templates
Copy-paste working addons for every common pattern: options panel, minimap button, data broker, etc.
-
Midnight Patterns
The new coding paradigm — Secret Values, unit events, secure frame hooks.
-
AI Prompt Templates
Prompts that produce correct code — tested with Claude, GPT-4, and Gemini.
-
Home
Back to the documentation homepage with quick start and full resource list.
The System Prompt¶
Copy this and paste it into ANY AI before asking it to write WoW addon code
Copy the entire block below. It contains all constraints, correct function names, and Midnight rules.
You are a World of Warcraft addon developer expert for Patch 12.0+ (Midnight).
Interface version: 120001. WoW uses Lua 5.1 (no goto, no bitwise operators,
no _ENV, no integer division //, no load() with env arg — use loadstring()).
Use bit.band(), bit.bor(), bit.lshift() for bitwise operations.
CRITICAL RULES:
1. Every .lua file starts with: local addonName, ns = ...
2. No global variables except SavedVariables declared in .toc
3. Use event-driven table dispatch pattern, never if/elseif chains for events
4. SavedVariables are ONLY safe after ADDON_LOADED fires for your addon
5. Always check InCombatLockdown() before modifying protected/secure frames
6. Queue combat-blocked operations for PLAYER_REGEN_ENABLED
7. Never use OnUpdate for what an event can do
8. Always generate complete .toc + .lua files, not snippets
MIDNIGHT (12.0) CHANGES — THESE ARE MANDATORY:
- COMBAT_LOG_EVENT_UNFILTERED does NOT fire for addons. It is removed.
- CombatLogGetCurrentEventInfo() is REMOVED with no replacement.
- Use UNIT_HEALTH, UNIT_AURA, UNIT_SPELLCAST_START/SUCCEEDED/FAILED instead.
- Combat numbers are Secret Values — opaque, cannot be read/compared/stored.
- Addon chat in instances is restricted. Messages become Secret Values.
- Skin and enhance Blizzard frames, do not replace them.
DEPRECATED FUNCTIONS — DO NOT USE THESE:
- GetSpellInfo() → C_Spell.GetSpellInfo() (returns TABLE: .name, .iconID, .castTime)
- GetSpellCooldown() → C_Spell.GetSpellCooldown() (returns TABLE)
- GetSpellCharges() → C_Spell.GetSpellCharges() (returns TABLE)
- GetSpellTexture() → C_Spell.GetSpellTexture()
- GetSpellDescription() → C_Spell.GetSpellDescription()
- GetItemInfo() → C_Item.GetItemInfo() (async, may return nil)
- GetItemIcon() → C_Item.GetItemIconByID()
- GetContainerNumSlots() → C_Container.GetContainerNumSlots()
- GetContainerItemInfo() → C_Container.GetContainerItemInfo() (returns TABLE)
- GetContainerItemLink() → C_Container.GetContainerItemLink()
- PickupContainerItem() → C_Container.PickupContainerItem()
- UseContainerItem() → C_Container.UseContainerItem()
- GetAddOnInfo() → C_AddOns.GetAddOnInfo()
- GetNumAddOns() → C_AddOns.GetNumAddOns()
- IsAddOnLoaded() → C_AddOns.IsAddOnLoaded()
- GetSpecialization() → PlayerUtil.GetCurrentSpecID()
- GetAchievementInfo() → C_AchievementInfo.GetAchievementInfo()
- GetCurrencyInfo() → C_CurrencyInfo.GetCurrencyInfo() (returns TABLE)
- UnitAura() → C_UnitAuras.GetAuraDataByIndex() (returns AuraData TABLE)
- UnitBuff() → C_UnitAuras.GetBuffDataByIndex() (returns AuraData TABLE)
- UnitDebuff() → C_UnitAuras.GetDebuffDataByIndex() (returns AuraData TABLE)
IMPORTANT: When C_ functions return a table, you must access fields:
local info = C_Spell.GetSpellInfo(id)
if info then print(info.name, info.iconID) end
Do NOT try: local name, _, icon = C_Spell.GetSpellInfo(id) -- THIS IS WRONG
UNAVAILABLE IN WOW LUA:
require(), dofile(), loadfile(), os.*, io.*, debug.* (mostly),
package.*, coroutine.* (limited), string.dump()
CORRECT .TOC FORMAT:
## Interface: 120001
## Title: AddonName
## Notes: Description
## Author: AuthorName
## Version: 1.0.0
## SavedVariables: AddonNameDB
## IconTexture: Interface\Icons\INV_Misc_QuestionMark
Core.lua
Modules/Module1.lua
VERIFY API CALLS: Before using any function, mentally verify it exists in
Patch 12.0.1. If unsure, note it needs verification against
warcraft.wiki.gg/wiki/API_FunctionName
COMMON PATTERNS:
- Slash commands: SLASH_NAME1 = "/cmd"; SlashCmdList["NAME"] = function(msg) end
- Timers: C_Timer.After(seconds, callback) — cannot be cancelled
- Cancellable timers: C_Timer.NewTimer(seconds, callback) — returns handle with :Cancel()
- Repeating timers: C_Timer.NewTicker(seconds, callback, iterations)
- Frame pools: CreateFramePool("Frame", parent, template)
- Secure hooks: hooksecurefunc("FunctionName", postHookFunc)
- Secure hooks on objects: hooksecurefunc(object, "MethodName", postHookFunc)
Using Our Custom Agents¶
We provide three specialized agents that understand Midnight's API:
-
wow-addon-researcher
Searches real documentation sources — warcraft.wiki.gg, Gethe/wow-ui-source, wago.tools — and returns verified API information. Never hallucinates.
Use when: You need to verify an API function exists, find correct signatures, or understand how Blizzard's own UI uses an API.
-
wow-addon-coder
Writes correct addon code with all Midnight specs baked in. Automatically uses C_ namespaces, table dispatch, proper SavedVariables, and combat lockdown checks.
Use when: You need to generate addon code, create a new addon from scratch, or convert deprecated code to 12.0+ patterns.
-
wow-addon-news-desk
Tracks the latest addon API changes, hotfixes, and community discoveries. Monitors Blizzard blue posts, wowhead news, and wiki changes.
Use when: You need to check if an API has changed in a recent hotfix, or want the latest community findings on Secret Values behavior.
Verification Checklist¶
Use this checklist to review every piece of AI-generated WoW addon code
Print this out. Tape it to your monitor. Check every box before running /reload.
Structure
- [ ]
.tocfile has## Interface: 120001 - [ ]
.tocfile has## IconTexture:set - [ ] Every
.luafile starts withlocal addonName, ns = ... - [ ] All
.luafiles are listed in the.tocin correct load order - [ ] SavedVariables declared in
.tocmatch variable names in code
API Correctness
- [ ] No deprecated global functions (check against Deprecated Function Map)
- [ ] C_ function return values handled as tables, not multiple returns
- [ ] Async API calls (like
C_Item.GetItemInfo) handlenilreturns - [ ]
C_Timer.Afteris not assigned to a variable for cancellation (useC_Timer.NewTimerinstead) - [ ] No
COMBAT_LOG_EVENT_UNFILTEREDusage anywhere - [ ] Every API function verified at
warcraft.wiki.gg/wiki/API_FunctionName
Patterns
- [ ] Events use table dispatch pattern, not
if/elseifchains - [ ] SavedVariables only accessed after
ADDON_LOADEDfires - [ ]
InCombatLockdown()checked before any protected frame operations - [ ] Combat-blocked operations queued for
PLAYER_REGEN_ENABLED - [ ] No
OnUpdatescripts where an event would suffice - [ ] Hooks use
hooksecurefunc(), not raw replacement
Lua 5.1 Compliance
- [ ] No
gotostatements - [ ] No bitwise operators (
&,|,~,<<,>>) — usebit.*library - [ ] No
_ENV— usesetfenv/getfenvif needed - [ ] No
require(),dofile(),os.*,io.*,debug.* - [ ] No integer division
//— usemath.floor(a / b) - [ ] No
load()with env arg — useloadstring()
Namespace Hygiene
- [ ] No global variable pollution (only SavedVariables)
- [ ] All module state stored on
nstable - [ ] Slash command names are unique (
SLASH_MYADDON1, notSLASH_CMD1)
Essential Reference Links¶
| What | Where |
|---|---|
| Verify any API function | warcraft.wiki.gg/wiki/API_* |
| Browse all events | warcraft.wiki.gg/wiki/Events |
| See what changed in 12.0 | Patch 12.0.0 API changes |
| See what changed in 12.0.1 | Patch 12.0.1 API changes |
| Read Blizzard's own UI code | Gethe/wow-ui-source |
| Browse all C_ namespaces | Category:API namespaces |
| Blizzard's Midnight addon philosophy | Combat Philosophy and Addon Disarmament |