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.

SurfaceClassic EraMidnightForever
World2004, frozenCurrent expansion2004 setting, new zones, cap 60
UI codeClassic APIMainline, 12.xMainline family, called Camelot in beta
Interface11500s120100 class16001 on the beta
Combat logAddons can read itRemoved for addonsRemoved for addons
Health mathsPlain numbersSecret in combatSecret observed even as a plain-looking number
Project idClassic1Observed 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:

  1. Call the API. You still get a value. Calling type() may say number, string, or boolean.
  2. Ask issecretvalue before you branch, compare, concatenate, format, or print.
  3. If it is secret, hand it to a widget that accepts secrets — StatusBar:SetValue is the known good path — or show a fixed label.
  4. If a protected action is illegal, do not wrap it in pcall and 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:

SurfaceVerdictWhy
Bags, bank, vendor, tooltipsBuildPresentation of data the UI already shows
Quest log, map pins, route notesBuildC_Map.SetUserWaypoint is the safe pin. Not the super-track call.
Nameplates and unit artBuild carefullyNAME_PLATE_UNIT_ADDED can fire before the unit exists. Sweep nameplate1 to nameplate40 a moment later.
Secret-safe personal stripBuildForever Beacon proof-of-concept is the reference
Damage meter, healing meterDo notBuilt-in meter. Secrets block the maths anyway.
Classic WeakAuras packsDo not portThe combat-automation model is what Midnight removed
Click-cast healer framesWaitSecure compilation failed on 69913 and may be a bug
Action bar paging via snippetsWaitSame 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.