Classic Era is permissive and old. Midnight is restrictive and current. Forever is the vanilla ruleset running the Midnight UI family. Write for that intersection or the addon will not load, or it will load and lie.
This pillar is part of the WoW Forever AI Addon Guide, working alongside our Forever API Cheatsheet, Common AI Addon Pitfalls, and Forever Beacon reference addon.
Three clients, one mistake
The most common trap for LLMs and developers alike is treating Forever as Classic Era with modern graphics, or as Retail Midnight without expansions. It is neither.
| Surface | Classic Era | Midnight | Forever |
|---|---|---|---|
| World | 2004, frozen | Current expansion | 2004 setting, new zones, cap 60 |
| UI code | Classic API | Mainline, 12.x | Mainline family, called Camelot in beta |
| Interface | 11500s | 120100 class | 16001 on the beta |
| Combat log | Addons can read it | Removed for addons | Removed for addons |
| Health maths | Plain numbers | Secret in combat | Secret observed even as a plain-looking number |
| Project id | Classic | 1 | Observed as 1 — the trap |
Detect Forever without the project id
WOW_PROJECT_ID is the wrong question. Capability plus the interface number is the right one, and both must be allowed to change. Hardcoding the internal name Camelot is also wrong: Blizzard confirmed it would be renamed before launch.
local function ForeverBeta()
local iface = select(4, GetBuildInfo())
-- 16001: Forever beta as published on the wiki TOC table.
-- Replace this when /dump select(4, GetBuildInfo()) changes.
return iface == 16001
end
local function HasSecrets()
return type(issecretvalue) == "function"
end
Compare this probe against the complete detection rules in the API reference table and watch for the traps documented in Twelve Traps to Avoid.
The secret pipeline
Midnight introduced secret values to restrict combat automation, and Forever inherits the entire pipeline:
- Call the API. You still get a value. Calling
type()may say number, string, or boolean. - Ask
issecretvaluebefore you branch, compare, concatenate, format, or print. - If it is secret, hand it to a widget that accepts secrets —
StatusBar:SetValueis the known good path — or show a fixed label. - If a protected action is illegal, do not wrap it in
pcalland declare victory. The failure can be silent and sticky.
Test you can paste in game
/run local h = UnitHealth("player"); print(type(h), pcall(function() return h + 1 end))
On build 69913 this printed number, false. A false from pcall means the addition was refused by the engine. For inspectable status-bar examples, use our code templates and the downloadable Beacon sample, then verify them on your build.
Auras are containers, not tables you own
Blizzard said the AuraContainer and AuraButton changes from 12.1.0 are available on Forever, and that later UI changes should land on both Standard and Forever. In combat, enumeration can throw and a per-spell lookup can answer nil. The pattern that survives is: tell the container the filter, skin the buttons, never read the aura payload to decide a play.
-- Do not walk auras with UnitAura or by index during combat.
-- Declare what the client may show, then skin the buttons it gives you.
-- AuraContainer arrived in 12.1 and Blizzard said Forever gets it too.
local container = CreateFrame("Frame", nil, parent, "AuraContainerTemplate")
-- Style AuraButtons you are handed.
-- If a lookup returns nil in combat, that nil is the API working.
What is worth building in 2026
Before prompting an AI or porting legacy code, know what the engine permits:
| Surface | Verdict | Why |
|---|---|---|
| Bags, bank, vendor, tooltips | Build | Presentation of data the UI already shows |
| Quest log, map pins, route notes | Build | C_Map.SetUserWaypoint is the safe pin. Not the super-track call. |
| Nameplates and unit art | Build carefully | NAME_PLATE_UNIT_ADDED can fire before the unit exists. Sweep nameplate1 to nameplate40 a moment later. |
| Secret-safe personal strip | Build | Forever Beacon proof-of-concept is the reference |
| Damage meter, healing meter | Do not | Built-in meter. Secrets block the maths anyway. |
| Classic WeakAuras packs | Do not port | The combat-automation model is what Midnight removed |
| Click-cast healer frames | Wait | Secure compilation failed on 69913 and may be a bug |
| Action bar paging via snippets | Wait | Same compile failure |
Porting order
From Midnight
Add 16001 to the interface line. Delete any "project id 1 means disable Classic quirks that Forever still needs" branch, especially rank handling: Forever is a level-60 world, so downranking still matters. Retest secrets outside instances. Midnight authors sometimes assumed secrets were instanced-only. Forever reports them more widely.
From Classic Era
Treat it as a new addon that may keep your SavedVariables schema. Replace every global item and spell call with the async C_Item and C_Spell APIs. Delete combat log listeners. Replace PlaySoundFile on interface oggs with PlaySound and a SOUNDKIT id. Replace faction checks that use UnitIsEnemy. Then read the file again looking for a percent sign next to health. For verified starter code, see our copy-paste code templates.
Beta defects versus design
Label these in your patch notes so players do not file them against your project:
- Secure snippet compilation failure: May be a beta bug. Retest before you rewrite a working Midnight click-cast into a weaker Forever one and call it permanent.
- SavedVariables loading failures: SavedVariables that save and do not load have a community workaround that injects the saved file as a TOC entry. That is a beta bandage. Do not fork your addon around it until launch still shows the bug.
- WOWPROJECTID of 1: May stay, because Forever really is in the Mainline family. Write as if it stays, relying on interface number and capability probing rather than project ID.