What's Possible: Pushing Boundaries in 12.0¶
The Great Addon Purge of 2026 didn't end addon development. It restarted it.
Midnight shipped with 437 new APIs, removed 138 old ones, and introduced 76 new events. The combat log is gone. Secret Values replaced raw data. Entire addon categories vanished overnight. And yet — two weeks into launch — the addon ecosystem is more inventive than it has been in years.
This page is about what's possible now. Not what was lost, but what's being built.
The New Landscape¶
A New World of APIs¶
Patch 12.0 was the largest API change in WoW's history. The numbers alone tell the story:
| Category | Count |
|---|---|
| New APIs added | 437 |
| APIs removed | 138 |
| New events | 76 |
| Events removed | 12 |
| New namespaces | 14 (including C_Housing, C_DurationUtil, C_CooldownViewer) |
But numbers undersell the shift. Midnight didn't just add and remove functions — it fundamentally changed what addon code is for. Before 12.0, addon code was an interpreter: read game state, make decisions, take actions. After 12.0, addon code is a stylist: receive opaque data, present it beautifully, get out of the way.
The Great Addon Purge of 2026¶
On March 2, 2026, hundreds of addons broke simultaneously. WeakAuras refused to ship. Hekili became mathematically impossible. GTFO went silent. Rotation helpers, combat log parsers, and data-driven boss mods all hit the Secret Values wall.
But from the wreckage came something unexpected: innovation under constraint.
- New addon categories emerged. Player housing launched with zero addon support on day one. Within 48 hours, HomeBound had its first release. Within two weeks, an entire housing addon ecosystem existed — decoration managers, layout importers, furniture catalogs.
- Old patterns evolved. ElvUI went from "UI replacement" to "UI skin." Details! went from "analytical powerhouse" to "beautiful meter display." Boss mods went from "mechanic solvers" to "timeline enhancers."
- New patterns were invented. Duration Objects, the combat queue pattern, swarm aura filtering, secret-safe StatusBar hooks — techniques that didn't exist six months ago are now standard practice.
The Window Is Open¶
Blizzard has announced an API freeze until Patch 12.0.5. The 437 new APIs, the 76 new events, the Secret Values system — all of it is stable until further notice. Season 1 launches March 17. Mythic raids unlock March 24. Millions of players are settling into Midnight right now, and the addon ecosystem has gaps the size of continents.
There has never been a better time to build a WoW addon.
Before & After — The 12.0 Paradigm Shift¶
The changes in Midnight aren't incremental. They represent a fundamental paradigm shift in how addons interact with the game. Here are the four biggest transformations, with concrete code comparisons.
Combat Data: From Raw to Curated¶
The most dramatic change. Before Midnight, addons had full access to combat data through COMBAT_LOG_EVENT_UNFILTERED and raw UnitHealth()/UnitPower() values. After Midnight, combat data flows through the Secret Values system — addons can display it, but cannot read, compare, or branch on it.
-- Full access to combat events and raw health values
local frame = CreateFrame("Frame")
frame:RegisterEvent("COMBAT_LOG_EVENT_UNFILTERED")
frame:SetScript("OnEvent", function(self, event)
local timestamp, subevent, _, sourceGUID, sourceName,
_, _, destGUID, destName, _, _, spellID, spellName,
_, amount = CombatLogGetCurrentEventInfo()
if subevent == "SPELL_DAMAGE" then
-- Read exact damage numbers
myDB.totalDamage = myDB.totalDamage + amount
-- Make decisions based on values
if amount > 50000 then
PlaySound(SOUNDKIT.RAID_WARNING)
end
end
end)
-- Read health directly, do percentage math
local hp = UnitHealth("target")
local maxHp = UnitHealthMax("target")
local pct = hp / maxHp
if pct < 0.2 then
ShowExecuteOverlay()
end
-- COMBAT_LOG_EVENT_UNFILTERED is gone for addons
-- Health values are secret in combat contexts
-- Instead: hook Blizzard's own display and use Duration Objects
-- Secret-safe health bar enhancement
hooksecurefunc(healthBar, "SetValue", function(self, value)
if self:IsForbidden() then return end
-- Apply class coloring (safe — doesn't read the value)
local unit = self.unit
if unit and UnitExists(unit) and UnitIsPlayer(unit) then
local _, class = UnitClass(unit)
local color = RAID_CLASS_COLORS[class]
if color then
self:SetStatusBarColor(color.r, color.g, color.b)
end
end
-- DO NOT do math on 'value' — it may be a secret
end)
-- Duration Objects for cooldown display
local cooldownDuration = C_Spell.GetSpellCooldownDuration(spellID)
cooldownFrame:SetCooldownFromDurationObject(cooldownDuration)
-- The widget handles the secret value internally
What this enables
Addons that focus on presentation — class-colored health bars, custom textures, restyled cooldown displays — work seamlessly. The data flows through Blizzard's pipeline; you style the output.
What this prevents
Automated decision-making based on combat state. No "if health below X, do Y." No combat log parsing for damage meters. No rotation helpers, no predictive healing, no automated callouts.
Addon Communication: From Always-On to Context-Aware¶
-- Send messages freely at any time
C_ChatInfo.SendAddonMessage("MYPREFIX", data, "RAID")
-- Receive and process immediately
local frame = CreateFrame("Frame")
frame:RegisterEvent("CHAT_MSG_ADDON")
frame:SetScript("OnEvent", function(self, event, prefix, msg, channel, sender)
if prefix == "MYPREFIX" then
ProcessRaidData(msg, sender) -- Always works
end
end)
-- Messages are throttled/blocked during active encounters in instances
-- Design for intermittent communication
local pendingMessages = {}
local function SafeSendMessage(prefix, data, channel)
-- Attempt to send; queue if we're in a restricted context
local success = pcall(C_ChatInfo.SendAddonMessage, prefix, data, channel)
if not success then
pendingMessages[#pendingMessages + 1] = {prefix, data, channel}
end
end
-- Flush pending messages when combat ends
local flusher = CreateFrame("Frame")
flusher:RegisterEvent("PLAYER_REGEN_ENABLED")
flusher:SetScript("OnEvent", function()
for i, msg in ipairs(pendingMessages) do
C_ChatInfo.SendAddonMessage(unpack(msg))
pendingMessages[i] = nil
end
end)
Design implication
Raid coordination addons (MRT, raid assignment tools) must now design for eventual consistency rather than real-time sync. Pre-pull data sharing works. Mid-encounter broadcasting does not.
UI Modification: From Replace to Enhance¶
-- Build an entirely parallel UI system
local myUnitFrame = CreateFrame("Frame", "MyCustomUnitFrame", UIParent)
local healthBar = CreateFrame("StatusBar", nil, myUnitFrame)
local powerBar = CreateFrame("StatusBar", nil, myUnitFrame)
local castBar = CreateFrame("StatusBar", nil, myUnitFrame)
local nameText = myUnitFrame:CreateFontString(nil, "OVERLAY")
-- Hide Blizzard's frame (causes taint in 12.0!)
PlayerFrame:Hide()
-- Register for all the events yourself
myUnitFrame:RegisterEvent("UNIT_HEALTH")
myUnitFrame:RegisterEvent("UNIT_POWER_UPDATE")
myUnitFrame:RegisterEvent("UNIT_AURA")
myUnitFrame:RegisterEvent("PLAYER_TARGET_CHANGED")
-- ... 20+ more events
-- Manually process everything
myUnitFrame:SetScript("OnEvent", function(self, event, ...)
-- Hundreds of lines of event handling
end)
-- Enhance Blizzard's existing frame — it handles all the data
local hiddenFrame = CreateFrame("Frame")
hiddenFrame:Hide()
-- Skin the player frame with post-hooks
hooksecurefunc("PlayerFrame_Update", function(self)
if self:IsForbidden() then return end
-- Strip default textures, apply custom look
for _, region in pairs({self:GetRegions()}) do
if region.SetTexture then
region:SetParent(hiddenFrame)
end
end
-- Apply a clean dark backdrop
if not self.backdropApplied then
Mixin(self, BackdropTemplateMixin)
self:SetBackdrop({
bgFile = "Interface\\Buttons\\WHITE8x8",
edgeFile = "Interface\\Buttons\\WHITE8x8",
edgeSize = 1,
})
self:SetBackdropColor(0.1, 0.1, 0.1, 0.85)
self:SetBackdropBorderColor(0, 0, 0, 1)
self.backdropApplied = true
end
end)
The golden rule
Use hooksecurefunc() to modify AFTER Blizzard renders. Use HookScript() instead of SetScript(). Use hidden frame reparenting instead of :Hide(). Store original state before modifying. Your addon becomes a cosmetic layer on top of a fully functional Blizzard frame.
Settings & Configuration: From LibDBIcon to Native¶
-- Required three libraries just to have settings
local AceDB = LibStub("AceDB-3.0")
local AceConfig = LibStub("AceConfig-3.0")
local AceConfigDialog = LibStub("AceConfigDialog-3.0")
local LibDBIcon = LibStub("LibDBIcon-1.0")
-- Complex options table
local options = {
type = "group",
args = {
enable = {
type = "toggle",
name = "Enable",
order = 1,
get = function() return db.enable end,
set = function(_, v) db.enable = v end,
},
scale = {
type = "range",
name = "Scale",
min = 0.5, max = 2.0, step = 0.05,
order = 2,
get = function() return db.scale end,
set = function(_, v) db.scale = v end,
},
},
}
AceConfig:RegisterOptionsTable("MyAddon", options)
AceConfigDialog:AddToBlizOptions("MyAddon")
-- Minimap icon required LibDBIcon + LibDataBroker
local ldb = LibStub("LibDataBroker-1.1"):NewDataObject("MyAddon", {
type = "launcher",
icon = "Interface\\Icons\\INV_Misc_Gear_01",
OnClick = function() AceConfigDialog:Open("MyAddon") end,
})
LibDBIcon:Register("MyAddon", ldb, db.minimap)
-- Zero libraries needed. Native Settings API + Addon Compartment.
local category = Settings.RegisterVerticalLayoutCategory("MyAddon")
-- Toggle
local enableSetting = Settings.RegisterAddOnSetting(category,
"MyAddon_Enable", "enable", MyAddonDB,
type(true), "Enable", true
)
Settings.CreateCheckbox(category, enableSetting, "Enable the addon.")
-- Slider
local scaleSetting = Settings.RegisterAddOnSetting(category,
"MyAddon_Scale", "scale", MyAddonDB,
type(1.0), "Scale", 1.0
)
local scaleOpts = Settings.CreateSliderOptions(0.5, 2.0, 0.05)
scaleOpts:SetLabelFormatter(MinimalSliderWithSteppersMixin.Label.Right)
Settings.CreateSlider(category, scaleSetting, scaleOpts, "Frame scale.")
Settings.RegisterAddOnCategory(category)
-- Minimap button: zero code — just TOC metadata
-- ## AddonCompartmentFunc: MyAddon_OnClick
-- ## IconTexture: Interface\Icons\INV_Misc_Gear_01
Why this matters
New addons can ship with professional settings panels and minimap integration using zero external libraries. No Ace3 dependency. No LibDataBroker. No LibDBIcon. The native API handles layout, scrolling, and persistence automatically.
Impressive Community Addons¶
Two weeks into Midnight, the community has already produced addons that showcase what's possible under the new rules. These are the standouts — not just for their functionality, but for the patterns they demonstrate.
HomeBound — The Housing Pioneer¶
| Downloads | 3M+ |
| Category | Player Housing |
| Why it matters | First major addon for an entirely new game system |
Midnight's player housing system launched with no addon API at all. Within the first week, Blizzard added C_Housing — a new namespace with 23 functions for querying furniture placement, room state, and visitor management. HomeBound was the first addon to capitalize on it.
HomeBound demonstrates the "build where Blizzard doesn't" philosophy. There's no existing UI to replace or enhance — player housing is virgin territory. The addon provides furniture search, layout saving, decoration catalogs, and visitor management that Blizzard's default housing UI simply doesn't offer.
The housing addon category didn't exist before March 2, 2026. Two weeks later, it includes HomeBound, Housing Decor Guide (tracking all 1,690 decoration items), and Advanced Decoration Tools (copy/paste, batch placement, smart rotation). An entire ecosystem built from nothing.
Platynator — Design Within the Box¶
| Downloads | Growing rapidly |
| Category | Nameplates |
| Why it matters | Built from scratch for Midnight constraints |
Platynator is the poster child for creative constraint. Where Plater v635 shipped with a list of features removed (NPC-specific coloring gone, aura-based scaling gone, fixate detection gone), Platynator was designed from the ground up knowing what the rules were.
The result is a nameplate addon that does less but feels better. Click to customize. Drag to reposition. No LUA scripting required. It targets the aesthetic-focused player who wants beautiful nameplates without touching combat data — and it delivers.
Northern Sky Raid Tools — Filling the WeakAuras Void¶
| Downloads | 3.2M+ |
| Category | Raid Tools |
| Why it matters | Replaces WeakAura raid packs within API constraints |
With WeakAuras gone, raid leaders needed something. Northern Sky Raid Tools stepped in with timer-based reminders, assignment displays, and cooldown tracking — all built within the Secret Values framework. It doesn't try to detect boss mechanics through combat parsing. Instead, it provides configurable timers that raiders can set up based on known boss ability schedules.
The addon represents a philosophical shift: from "the addon tells you what's happening" to "you tell the addon when things happen, and it helps you communicate that to your team."
ElvUI v15.0.0 — The Great Transformation¶
| Downloads | 200M+ lifetime |
| Category | UI Suite |
| Why it matters | The most dramatic adaptation in addon history |
ElvUI's journey to Midnight is a story in itself. In October 2025, the team announced they were cancelling Midnight support entirely. In December 2025, they reversed course. On launch day, v15.0.0 shipped — with Style Filters and Portraits removed, but the core UI skin intact.
The transformation from "UI replacement" to "UI skin" required rearchitecting the addon's relationship with Blizzard frames. Where ElvUI previously created parallel frame hierarchies, it now hooks and reskins the default ones. The result is an ElvUI that works with Edit Mode, respects combat lockdown, and survives patches without emergency hotfixes.
Better Timeline — Enhancing the Native Boss Timeline¶
| Downloads | Growing |
| Category | Boss Encounter |
| Why it matters | Enhancement pattern applied to Blizzard's newest system |
Blizzard's native Boss Ability Timeline was designed specifically to replace what boss mods used to do. Better Timeline takes that native timeline and makes it better — custom colors, adjustable sizing, ability filtering, and layout options that Blizzard's default doesn't offer.
This is the purest expression of the Enhancement Artist philosophy. Blizzard builds the system. Blizzard provides the data. The addon makes it look and work the way the player wants. No combat parsing needed. No Secret Values conflicts. Just pure UI enhancement.
Techniques That Push Boundaries¶
These are the advanced patterns that power the most impressive Midnight addons. Each one solves a specific constraint in a creative way.
The Metatable Hook Pattern¶
For cases where hooksecurefunc() isn't enough — when you need to intercept method calls on all instances of a frame type, not just known frames:
-- Hook ALL StatusBars, including ones created after your addon loads
local StatusBarMeta = getmetatable(CreateFrame("StatusBar")).__index
-- Wrap with pcall for safety — metatable hooks can be fragile
hooksecurefunc(StatusBarMeta, "SetStatusBarColor", function(self, r, g, b, a)
if self:IsForbidden() then return end
-- This fires for EVERY StatusBar in the game
-- Use frame name or parent checks to filter
if self:GetParent() and self:GetParent().unit then
-- This is likely a unit frame health/power bar
-- Apply your enhancement
end
end)
When to use metatable hooks
Use them when you need to catch frames that don't exist yet at addon load time — dynamically created nameplates, party frames that appear when groups form, or ScrollBox elements that are created on demand. Always wrap in safety checks.
When NOT to use metatable hooks
Never use them to override behavior. Only post-hook. And always include IsForbidden() checks — metatable hooks will fire on forbidden frames that would crash the client if modified.
Duration Objects & Secret Values Display¶
The new way to display time-based combat information without touching secret values directly:
-- Create a duration display that respects Secret Values
local function CreateDurationDisplay(parent, spellID)
local display = parent:CreateFontString(nil, "OVERLAY", "GameFontNormal")
-- Get a Duration Object — it's opaque but displayable
local function Update()
local cooldownDuration = C_Spell.GetSpellCooldownDuration(spellID)
if cooldownDuration then
-- Pass directly to a Cooldown widget — no math needed
parent.cooldown:SetCooldownFromDurationObject(cooldownDuration)
end
end
-- Use C_Timer for periodic updates (not OnUpdate — too expensive)
C_Timer.NewTicker(0.1, Update)
return display
end
Duration Objects are the key abstraction
C_DurationUtil.CreateDuration() and related APIs let you pass timing data to UI widgets without ever reading the underlying numbers. The widget handles the countdown internally. This is how tullaCTC, OmniCC, and CDM addons display cooldown text in Midnight.
The Combat Queue Pattern¶
The foundational pattern for any addon that modifies secure frames. Queue operations during combat, flush when combat ends:
local combatQueue = {}
local isCombat = false
local function RunAfterCombat(fn)
if InCombatLockdown() then
combatQueue[#combatQueue + 1] = fn
return false -- operation was queued
end
fn()
return true -- operation ran immediately
end
-- Flush queue on combat end
local queueFrame = CreateFrame("Frame")
queueFrame:RegisterEvent("PLAYER_REGEN_ENABLED")
queueFrame:RegisterEvent("PLAYER_REGEN_DISABLED")
queueFrame:SetScript("OnEvent", function(self, event)
if event == "PLAYER_REGEN_DISABLED" then
isCombat = true
elseif event == "PLAYER_REGEN_ENABLED" then
isCombat = false
for i = 1, #combatQueue do
local ok, err = pcall(combatQueue[i])
if not ok then
-- Log but don't break the queue
geterrorhandler()(err)
end
combatQueue[i] = nil
end
end
end)
Every enhancement addon needs this
If you call SetPoint(), SetParent(), SetSize(), or any other secure frame method during combat lockdown, you will taint the frame and potentially break combat functionality for the player. The combat queue pattern is not optional — it is mandatory.
Swarm Aura Filtering¶
Midnight introduced four new aura filter categories that give addons more granular control over buff and debuff display:
-- New filter categories in 12.0
local AURA_FILTERS = {
"HELPFUL", -- Buffs (existing)
"HARMFUL", -- Debuffs (existing)
"PLAYER", -- Cast by player (existing)
"RAID", -- Raid-wide auras (existing)
"CROWD_CONTROL", -- NEW: Stuns, roots, fears, polymorphs, hex
"BIG_DEFENSIVE", -- NEW: Major defensives (Divine Shield, Ice Block)
"RAID_PLAYER_DISPELLABLE", -- NEW: Debuffs you can dispel with your class
"RAID_IN_COMBAT", -- NEW: Auras relevant during combat
}
-- Example: Show only purgeable enemy buffs on nameplates
local function GetPurgeableAuras(unit)
local auras = {}
for i = 1, 40 do
local auraData = C_UnitAuras.GetBuffDataByIndex(unit, i, "HELPFUL|STEALABLE")
if not auraData then break end
auras[#auras + 1] = auraData
end
return auras
end
The Object Pool Pattern¶
For addons that create and destroy many frames dynamically (nameplate indicators, scrolling text, buff icons), object pooling prevents garbage collection stalls:
local FramePool = {}
FramePool.__index = FramePool
function FramePool:New(frameType, parent, template)
return setmetatable({
frameType = frameType,
parent = parent,
template = template,
active = {},
inactive = {},
}, self)
end
function FramePool:Acquire()
local frame = tremove(self.inactive)
if not frame then
frame = CreateFrame(self.frameType, nil, self.parent, self.template)
end
self.active[frame] = true
frame:Show()
return frame
end
function FramePool:Release(frame)
self.active[frame] = nil
frame:Hide()
frame:ClearAllPoints()
self.inactive[#self.inactive + 1] = frame
end
function FramePool:ReleaseAll()
for frame in pairs(self.active) do
self:Release(frame)
end
end
-- Usage: pooling nameplate indicators
local indicatorPool = FramePool:New("Frame", UIParent, "BackdropTemplate")
hooksecurefunc("CompactUnitFrame_UpdateAuras", function(frame)
if frame:IsForbidden() then return end
-- Release old indicators, acquire new ones from pool
if frame.__indicators then
for _, indicator in ipairs(frame.__indicators) do
indicatorPool:Release(indicator)
end
end
frame.__indicators = {}
-- Create indicators for visible auras
for i = 1, 4 do
local indicator = indicatorPool:Acquire()
indicator:SetParent(frame)
indicator:SetSize(8, 8)
indicator:SetPoint("BOTTOMRIGHT", frame, "BOTTOMRIGHT", -2 - (i-1) * 10, 2)
frame.__indicators[i] = indicator
end
end)
When to pool
Use object pooling when frames are created and destroyed frequently — nameplate add-ons, scrolling combat text replacements, dynamic buff displays. For static frames (settings panels, main addon windows), direct CreateFrame() is fine.
What's Coming Next¶
The API Freeze¶
Blizzard has confirmed that the current API surface is frozen until Patch 12.0.5. No new removals. No new restrictions. No new Secret Values categories. The 437 new APIs, the Duration Object system, the Housing namespace — all stable.
This is the development window. Every addon built today will continue working through Season 1, through Mythic race, through the first content patch. The foundation is set.
Predicted Developments in 12.0.5¶
Based on Blizzard's public statements, community manager responses, and patterns from previous expansions:
- More Housing APIs. The
C_Housingnamespace shipped with 23 functions. Player housing is Midnight's flagship feature. Expect furniture crafting integration, visitor permissions, and possibly housing plot marketplace APIs. - Potential Secret Values relaxation. Blizzard has loosened Secret Values restrictions multiple times during alpha and beta. Community pressure — particularly from the accessibility community — may lead to further relaxation for personal combat state.
- Cooldown Manager expansion. The CDM received major updates in 11.1.5 and 11.2.5. More customization hooks and condition APIs are likely.
WeakAuras: The Conditions for Return¶
The WeakAuras team has publicly stated three conditions that would allow them to ship a Midnight version:
- Secret value arithmetic — the ability to compute new secret values from existing ones (e.g.,
secretA + secretB = secretC) - Personal combat state exemption — reverting restrictions for the player's own combat data (not other players')
- Complete system reversal — rolling back Secret Values entirely (deemed unlikely by the team)
As of March 2026, none of these conditions have been met. But the door remains open.
Start Building¶
The Opportunity¶
The addon ecosystem has gaps everywhere. Player housing needs better tools. The Cooldown Manager needs more skins. Edit Mode needs more registered frames. Nameplates need more enhancers. Boss timelines need more customization. The Settings API is underutilized. The Addon Compartment is half-empty.
Every one of these is an addon waiting to be built.
Where to Go From Here¶
| Resource | What You'll Find |
|---|---|
| Plugin Overview | How the better-addons AI toolkit works |
| Coding for Midnight | Copy-pasteable code patterns for every 12.0 scenario |
| Building Better Addons | The enhancement ecosystem and how to join it |
| Blizzard Systems | Edit Mode, CDM, Settings API, Addon Compartment |
| Code Templates | Complete starter templates for common addon types |
| Cutting Edge | The latest news from the addon trenches |
The Best Time to Build¶
The API is frozen. The player base is massive. The competition is thin. The old guard is diminished or adapting. The tools — including the AI-powered development workflow you're reading about right now — have never been better.
The best addons of the Midnight era haven't been written yet.
Build one.