Describe an addon in one sentence and download it as a ready-to-install zip. It is free, needs no account, and every global function and event it uses is checked against the API list from Forever build 70338.
Ask Our Sweaty Degenerate AI About Addon Maker
Describe Your Addon, What We Check, Fix It After a Patch, Checked Examples & More
Anything our guides cover. Answers come from this page and the rest of our guides, with the real values charted and the guide sections linked. It's AI, so check the source before you quote it.
Try
Describe Your Addon
Free. No signup. You keep the code. Broke after a patch? Paste the error and we fix it.
Custom builds are turned off. The eight checked examples still download.
Pick an example, or describe your own.
Your Addon
Not tested in game yet. Client 1.60.1.70338.
AI Disclaimer: made with the WoW Forever Guides Addon Maker (Claude), checked against the Forever client API list.
local ADDON = ...
local NAME = "GuildChatFilter"
local errors = {}
local function safeCall(func)
return xpcall(func, function(err)
errors[#errors + 1] = tostring(err)
if #errors > 10 then table.remove(errors, 1) end
print(NAME .. " error: " .. tostring(err))
end)
end
local function showErrors()
if #errors == 0 then print(NAME .. ": no errors saved") return end
for i = 1, #errors do print(i .. ". " .. errors[i]) end
end
local seen = {}
local function repeatSpam(message)
local now = GetTime()
local count = 0
local kept = {}
for i = 1, #seen do
if now - seen[i].at < 30 then
kept[#kept + 1] = seen[i]
if seen[i].text == message then count = count + 1 end
end
end
kept[#kept + 1] = { text = message, at = now }
seen = kept
return count >= 2
end
local function filter(_, _, message)
if type(message) ~= "string" or message == "" then return false end
local hide = false
safeCall(function() hide = repeatSpam(message) end)
return hide
end
ChatFrame_AddMessageEventFilter("CHAT_MSG_GUILD", filter)
SLASH_GUILDCHATFILTER1 = "/guildfilter"
SlashCmdList["GUILDCHATFILTER"] = function(msg)
if msg == "errors" then showErrors() else print(NAME .. ": hiding guild lines that repeat within 30 seconds") end
end
Make Quest Items Sparkle
Lists quest items in your bags on a gold line. Forever has no safe bag-button sparkle here, so the list is the signal.
No checker errors. Not tested in game yet. Client 1.60.1.70338.
local ADDON = ...
local NAME = "QuestItemSparkle"
local errors = {}
local function safeCall(func)
return xpcall(func, function(err)
errors[#errors + 1] = tostring(err)
if #errors > 10 then table.remove(errors, 1) end
print(NAME .. " error: " .. tostring(err))
end)
end
local function showErrors()
if #errors == 0 then print(NAME .. ": no errors saved") return end
for i = 1, #errors do print(i .. ". " .. errors[i]) end
end
local frame = CreateFrame("Frame", "QuestItemSparkleFrame", UIParent)
frame:SetSize(240, 72)
frame:SetPoint("CENTER", UIParent, "CENTER", 0, 160)
frame:SetMovable(true)
frame:EnableMouse(true)
frame:RegisterForDrag("LeftButton")
frame:SetScript("OnDragStart", function(self) self:StartMoving() end)
frame:SetScript("OnDragStop", function(self) self:StopMovingOrSizing() end)
local label = frame:CreateFontString(nil, "OVERLAY", "GameFontNormal")
label:SetPoint("CENTER")
label:SetText("QuestItemSparkleFrame")
local function scan()
local lines = {}
for bag = 0, 4 do
local slots = C_Container.GetContainerNumSlots(bag) or 0
for slot = 1, slots do
local isQuest = C_Container.GetContainerItemQuestInfo(bag, slot)
if isQuest then
local info = C_Container.GetContainerItemInfo(bag, slot)
local name = info and info.itemID and C_Item.GetItemInfo(info.itemID) or nil
lines[#lines + 1] = name or "Quest item"
end
end
end
if #lines == 0 then label:SetText("No quest items in your bags")
else label:SetText(table.concat(lines, "\n")) end
end
frame:RegisterEvent("BAG_UPDATE")
frame:RegisterEvent("PLAYER_LOGIN")
frame:SetScript("OnEvent", function() safeCall(scan) end)
SLASH_QUESTITEMSPARKLE1 = "/questitems"
SlashCmdList["QUESTITEMSPARKLE"] = function(msg)
if msg == "errors" then showErrors() else scan() end
end
Auto-Decline Party Invites
Declines a party invite as it arrives. Turn it off with /declineinvites off.
No checker errors. Not tested in game yet. Client 1.60.1.70338.
local ADDON = ...
local NAME = "DeclineInvites"
local errors = {}
local function safeCall(func)
return xpcall(func, function(err)
errors[#errors + 1] = tostring(err)
if #errors > 10 then table.remove(errors, 1) end
print(NAME .. " error: " .. tostring(err))
end)
end
local function showErrors()
if #errors == 0 then print(NAME .. ": no errors saved") return end
for i = 1, #errors do print(i .. ". " .. errors[i]) end
end
local on = true
local frame = CreateFrame("Frame")
frame:RegisterEvent("PARTY_INVITE_REQUEST")
frame:SetScript("OnEvent", function()
if not on then return end
safeCall(function()
DeclineGroup()
StaticPopup_Hide("PARTY_INVITE")
print(NAME .. ": declined a party invite")
end)
end)
SLASH_DECLINEINVITES1 = "/declineinvites"
SlashCmdList["DECLINEINVITES"] = function(msg)
if msg == "errors" then showErrors() return end
if msg == "off" then on = false print(NAME .. ": off") return end
on = true
print(NAME .. ": on")
end
Count Every Mob I've Killed
Counts party kills on this character, including a no-XP kill and a grouped kill. Counts stay in SavedVariables.
No checker errors. Not tested in game yet. Client 1.60.1.70338.
## Interface: 16001
## Title: Local Kill Count
## Notes: Local kill counts. No mob database.
## Author: You
## Version: 1.0.0
## X-Generator: WoW Forever Guides Addon Maker
## X-License: CC0 (public domain)
## SavedVariables: LocalKillCountDB
LocalKillCount.lua
LocalKillCount/LocalKillCount.lua
local ADDON = ...
local NAME = "LocalKillCount"
local errors = {}
local function safeCall(func)
return xpcall(func, function(err)
errors[#errors + 1] = tostring(err)
if #errors > 10 then table.remove(errors, 1) end
print(NAME .. " error: " .. tostring(err))
end)
end
local function showErrors()
if #errors == 0 then print(NAME .. ": no errors saved") return end
for i = 1, #errors do print(i .. ". " .. errors[i]) end
end
LocalKillCountDB = LocalKillCountDB or { total = 0, xp = 0, gray = 0, grouped = 0 }
local function bump(key)
LocalKillCountDB[key] = (LocalKillCountDB[key] or 0) + 1
end
local frame = CreateFrame("Frame")
frame:RegisterEvent("PARTY_KILL")
frame:SetScript("OnEvent", function()
safeCall(function()
local before = UnitXP("player")
local grouped = IsInGroup()
C_Timer.After(0.5, function()
safeCall(function()
bump("total")
if grouped then bump("grouped") end
local after = UnitXP("player")
if issecretvalue(before) then return end
if issecretvalue(after) then return end
if after == before then bump("gray") else bump("xp") end
end)
end)
end)
end)
local function report()
local db = LocalKillCountDB
print(NAME .. ": " .. (db.total or 0) .. " kills, " .. (db.xp or 0) .. " with XP, " .. (db.gray or 0) .. " with no XP, " .. (db.grouped or 0) .. " while grouped")
end
SLASH_LOCALKILLCOUNT1 = "/kills"
SlashCmdList["LOCALKILLCOUNT"] = function(msg)
if msg == "errors" then showErrors() else report() end
end
Vendor Price on Every Tooltip
Adds the vendor sell price to an item tooltip, or a line that the price is not loaded yet.
No checker errors. Not tested in game yet. Client 1.60.1.70338.
local ADDON = ...
local NAME = "VendorPriceTip"
local errors = {}
local function safeCall(func)
return xpcall(func, function(err)
errors[#errors + 1] = tostring(err)
if #errors > 10 then table.remove(errors, 1) end
print(NAME .. " error: " .. tostring(err))
end)
end
local function showErrors()
if #errors == 0 then print(NAME .. ": no errors saved") return end
for i = 1, #errors do print(i .. ". " .. errors[i]) end
end
local function coins(copper)
if type(copper) ~= "number" or copper <= 0 then return nil end
local gold = math.floor(copper / 10000)
local silver = math.floor((copper % 10000) / 100)
local rest = copper % 100
return gold .. "g " .. silver .. "s " .. rest .. "c"
end
local function addPrice(tip)
safeCall(function()
local _, link = tip:GetItem()
if not link then return end
local sell = select(11, C_Item.GetItemInfo(link))
local line = coins(sell)
if not line then
C_Item.RequestLoadItemDataByID(link)
line = "Vendor price not loaded"
end
tip:AddLine(line, 1, 0.82, 0.45)
end)
end
GameTooltip:HookScript("OnTooltipSetItem", addPrice)
if TooltipDataProcessor and TooltipDataProcessor.AddTooltipPostCall and Enum and Enum.TooltipDataType and Enum.TooltipDataType.Item then
TooltipDataProcessor.AddTooltipPostCall(Enum.TooltipDataType.Item, addPrice)
end
SLASH_VENDORPRICETIP1 = "/vendorprice"
SlashCmdList["VENDORPRICETIP"] = function(msg)
if msg == "errors" then showErrors() else print(NAME .. ": vendor price is added to item tooltips") end
end
XP per Hour and Time to Next Level
Shows XP per hour and a rough time to level. Idle time and a brand-new level do not divide by zero.
No checker errors. Not tested in game yet. Client 1.60.1.70338.
## Interface: 16001
## Title: XP Per Hour
## Notes: XP per hour and time to level.
## Author: You
## Version: 1.0.0
## X-Generator: WoW Forever Guides Addon Maker
## X-License: CC0 (public domain)
XpPerHour.lua
XpPerHour/XpPerHour.lua
local ADDON = ...
local NAME = "XpPerHour"
local errors = {}
local function safeCall(func)
return xpcall(func, function(err)
errors[#errors + 1] = tostring(err)
if #errors > 10 then table.remove(errors, 1) end
print(NAME .. " error: " .. tostring(err))
end)
end
local function showErrors()
if #errors == 0 then print(NAME .. ": no errors saved") return end
for i = 1, #errors do print(i .. ". " .. errors[i]) end
end
local frame = CreateFrame("Frame", "XpPerHourFrame", UIParent)
frame:SetSize(240, 72)
frame:SetPoint("CENTER", UIParent, "CENTER", 0, 160)
frame:SetMovable(true)
frame:EnableMouse(true)
frame:RegisterForDrag("LeftButton")
frame:SetScript("OnDragStart", function(self) self:StartMoving() end)
frame:SetScript("OnDragStop", function(self) self:StopMovingOrSizing() end)
local label = frame:CreateFontString(nil, "OVERLAY", "GameFontNormal")
label:SetPoint("CENTER")
label:SetText("XpPerHourFrame")
local state = { start = nil, xp = nil, last = nil }
local function paint(now)
local xp = UnitXP("player")
local maxXp = UnitXPMax("player")
if issecretvalue(xp) then label:SetText("XP is hidden right now") return end
if issecretvalue(maxXp) then label:SetText("XP is hidden right now") return end
if not state.start or not state.xp then
state.start = now
state.xp = xp
state.last = now
label:SetText("Waiting for XP")
return
end
if now - state.last > 120 then label:SetText("Idle. No recent XP.") return end
local gained = xp - state.xp
local elapsed = now - state.start
if gained <= 0 or elapsed < 1 then label:SetText("Waiting for XP") return end
local perHour = gained / elapsed * 3600
if perHour <= 0 then label:SetText("Waiting for XP") return end
local remain = maxXp - xp
if remain < 0 then remain = 0 end
local minutes = math.floor(remain / perHour * 60)
label:SetText(math.floor(perHour) .. " XP/hour, about " .. minutes .. " min")
end
local elapsed = 0
frame:SetScript("OnUpdate", function(_, gap)
elapsed = elapsed + gap
if elapsed < 1 then return end
elapsed = 0
safeCall(function() paint(GetTime()) end)
end)
frame:RegisterEvent("PLAYER_XP_UPDATE")
frame:RegisterEvent("PLAYER_LEVEL_UP")
frame:SetScript("OnEvent", function(_, event)
safeCall(function()
if event == "PLAYER_LEVEL_UP" then state.start = nil state.xp = nil state.last = nil end
if event == "PLAYER_XP_UPDATE" then state.last = GetTime() end
paint(GetTime())
end)
end)
SLASH_XPPERHOUR1 = "/xphour"
SlashCmdList["XPPERHOUR"] = function(msg)
if msg == "errors" then showErrors() else paint(GetTime()) print(NAME .. ": " .. (label:GetText() or "")) end
end
frame:Show()
Remind Me When My Buff Drops
Out of combat, shows the buff name you set when that buff is missing. It stays hidden in combat.
No checker errors. Not tested in game yet. Client 1.60.1.70338.
## Interface: 16001
## Title: Buff Reminder
## Notes: Out-of-combat reminder when one buff is missing.
## Author: You
## Version: 1.0.0
## X-Generator: WoW Forever Guides Addon Maker
## X-License: CC0 (public domain)
## SavedVariables: BuffReminderDB
BuffReminder.lua
BuffReminder/BuffReminder.lua
local ADDON = ...
local NAME = "BuffReminder"
local errors = {}
local function safeCall(func)
return xpcall(func, function(err)
errors[#errors + 1] = tostring(err)
if #errors > 10 then table.remove(errors, 1) end
print(NAME .. " error: " .. tostring(err))
end)
end
local function showErrors()
if #errors == 0 then print(NAME .. ": no errors saved") return end
for i = 1, #errors do print(i .. ". " .. errors[i]) end
end
local frame = CreateFrame("Frame", "BuffReminderFrame", UIParent)
frame:SetSize(240, 72)
frame:SetPoint("CENTER", UIParent, "CENTER", 0, 160)
frame:SetMovable(true)
frame:EnableMouse(true)
frame:RegisterForDrag("LeftButton")
frame:SetScript("OnDragStart", function(self) self:StartMoving() end)
frame:SetScript("OnDragStop", function(self) self:StopMovingOrSizing() end)
local label = frame:CreateFontString(nil, "OVERLAY", "GameFontNormal")
label:SetPoint("CENTER")
label:SetText("BuffReminderFrame")
BuffReminderDB = BuffReminderDB or { spell = "" }
frame:Hide()
local function remind()
if InCombatLockdown() or UnitAffectingCombat("player") then frame:Hide() return end
local spell = BuffReminderDB.spell or ""
if spell == "" then frame:Hide() return end
local aura = C_UnitAuras.GetAuraDataBySpellName("player", spell, "HELPFUL")
if issecretvalue(aura) then frame:Hide() return end
if aura then frame:Hide() return end
label:SetText("Buff down: " .. spell)
frame:Show()
end
frame:RegisterEvent("UNIT_AURA")
frame:RegisterUnitEvent("UNIT_AURA", "player")
frame:RegisterEvent("PLAYER_REGEN_DISABLED")
frame:RegisterEvent("PLAYER_REGEN_ENABLED")
frame:SetScript("OnEvent", function() safeCall(remind) end)
SLASH_BUFFREMINDER1 = "/buffremind"
SlashCmdList["BUFFREMINDER"] = function(msg)
if msg == "errors" then showErrors() return end
if msg == "" then print(NAME .. ": /buffremind Spell Name") return end
BuffReminderDB.spell = msg
print(NAME .. ": watching " .. msg)
remind()
end
Raid Prep Checklist for My Guild
A personal checklist you tick before a raid. It does not read the guild roster.
No checker errors. Not tested in game yet. Client 1.60.1.70338.
## Interface: 16001
## Title: Raid Prep List
## Notes: A personal raid prep checklist.
## Author: You
## Version: 1.0.0
## X-Generator: WoW Forever Guides Addon Maker
## X-License: CC0 (public domain)
## SavedVariables: RaidPrepDB
RaidPrepList.lua
RaidPrepList/RaidPrepList.lua
local ADDON = ...
local NAME = "RaidPrepList"
local errors = {}
local function safeCall(func)
return xpcall(func, function(err)
errors[#errors + 1] = tostring(err)
if #errors > 10 then table.remove(errors, 1) end
print(NAME .. " error: " .. tostring(err))
end)
end
local function showErrors()
if #errors == 0 then print(NAME .. ": no errors saved") return end
for i = 1, #errors do print(i .. ". " .. errors[i]) end
end
local frame = CreateFrame("Frame", "RaidPrepFrame", UIParent)
frame:SetSize(240, 72)
frame:SetPoint("CENTER", UIParent, "CENTER", 0, 160)
frame:SetMovable(true)
frame:EnableMouse(true)
frame:RegisterForDrag("LeftButton")
frame:SetScript("OnDragStart", function(self) self:StartMoving() end)
frame:SetScript("OnDragStop", function(self) self:StopMovingOrSizing() end)
local label = frame:CreateFontString(nil, "OVERLAY", "GameFontNormal")
label:SetPoint("CENTER")
label:SetText("RaidPrepFrame")
RaidPrepDB = RaidPrepDB or { items = { "Food", "Water", "Flasks", "Repair" }, done = {} }
local function line()
local parts = {}
for i = 1, #RaidPrepDB.items do
local item = RaidPrepDB.items[i]
local mark = RaidPrepDB.done[item] and "[x] " or "[ ] "
parts[#parts + 1] = mark .. item
end
if #parts == 0 then label:SetText("Checklist is empty") else label:SetText(table.concat(parts, "\n")) end
end
frame:SetScript("OnMouseUp", function()
safeCall(function()
if #RaidPrepDB.items == 0 then return end
local item = RaidPrepDB.items[1]
for i = 1, #RaidPrepDB.items do
if not RaidPrepDB.done[RaidPrepDB.items[i]] then item = RaidPrepDB.items[i] break end
end
RaidPrepDB.done[item] = not RaidPrepDB.done[item]
line()
end)
end)
line()
SLASH_RAIDPREPLIST1 = "/raidprep"
SlashCmdList["RAIDPREPLIST"] = function(msg)
if msg == "errors" then showErrors() return end
local add = msg:match("^add%s+(.+)$")
if add then
RaidPrepDB.items[#RaidPrepDB.items + 1] = add
line()
print(NAME .. ": added " .. add)
return
end
if msg == "reset" then RaidPrepDB.done = {} line() print(NAME .. ": cleared") return end
line()
print(NAME .. ": click the box to tick the next open line")
end
Custom builds are off. The checked examples still download.
The maker is at its cap for now. The checked examples still download.
You have used today's builds or changes. The checked examples still download.
The check did not pass. Try again. The examples still download.
Custom builds are not set up on this server. The examples still download.
The maker did not finish. Your prompt is still in the box.
That build took too long and was stopped. Your prompt is still in the box.
The connection dropped. Your prompt and the last good addon are still here.
Stopped. Your prompt is still in the box.
That link expired or the saved copy was deleted.
This browser did not save that addon, so it cannot change it.
Refresh the page and try from wowforeverguides.com.
That request was not usable. Keep the sentence short and try again.
A custom build needs the challenge script. The examples still download.
Building. This can take a minute.
Checking the files and repairing problems.
Saved on this browser for 90 days. Anyone with the share link can read the latest copy.
This browser blocked storage. The owner key lasts for this tab only, and this addon is not saved.
The on-this-browser list could not be stored.
Copied.
Select the code and copy it yourself.
The saved copy is deleted.
Delete it
Share link copied. It does not include your owner key.
The last check failed, so there is no download. The previous good copy is unchanged.
That request was refused.
One question before the build.
A published addon already does this.
Shared addon. Read only.
Change and fix run on a saved build. Describe your own addon to get one.
Checked example. Not tested in game yet.
Addons can show information the client already gives you. They cannot play the game for you.Blizzard: Developer Q&A, September 17, 2026. The lists below match what the eight examples do. Read what works and what breaks before you rely on one.
Yes. Daily limits keep it free for everyone. The eight examples download with no account.
Do I need to know Lua?
No. You can read every file if you want. Each example is a .toc and one .lua file.
Is AI-written addon code safe?
A custom build has no network calls, and you can read the source. The checker compares every global function and event with the API list from Forever build 70338 and with Blizzard's addon policy, and warns on widget methods it cannot confirm. That is not a promise the addon is safe in combat, and none of these files has been tested in game yet.
Are addons allowed in WoW Forever?
Yes, on the modern API, with Midnight's combat limits. The addons page has the September 17 answer.
Why can't it build a rotation or auto-cast addon?
The maker does not generate combat automation or code that tries to bypass protected actions, which the game blocks. It checks every build against Blizzard's UI add-on policy. Ask for a note, a checklist, a tooltip line, or an out-of-combat reminder instead.
Does WeakAuras work in Forever?
Wowhead wrote that WeakAuras stopped development before Midnight's addon changes. A different author listed a fork, and Musca Auras publishes a Forever file. Blizzard has not said WeakAuras itself runs. The addons page has those reports.
Can I share or publish my addon?
Yes. The example files are public domain, CC0. Use them, change them, and publish them. No credit is required. Paste this line where a site asks for an AI disclosure. AI Disclaimer: made with the WoW Forever Guides Addon Maker (Claude), checked against the Forever client API list.
What if it breaks after a patch?
Paste the error into Fix it. If the new copy fails the check, the last good copy stays and there is no download of the failed one.
Which AI writes it?
Claude, from Anthropic, writes a custom draft. Our checker then checks every global function and event against the build 70338 API list and flags widget methods it cannot confirm. Nothing has been tested in game yet, so read the files before you rely on one.
What do you store?
The prompt and the files, for 90 days, and you can delete them. The privacy page says what the share link shows and what stays in this browser.
Names and events are checked against client 1.60.1.70338. These files have not been tested in game.
Roll on analytics? Need = Clicky, GA4 & Microsoft Clarity with cookies and session replays, to see which guides help. Pass = no analytics at all.Privacy