Wowpedia
Wowpedia
229,636
pages

The AddOn system was designed by Slouken. Prior to Patch 2.0.1 you could even write an addon to be a full rotation and leveling bot or one that would travel for you. So how do you create a chat command? Play a John Cena sound on levelup? Automatically greet and annoy your guild members?

Getting started

For this tutorial you will need:

Hello World

Running scripts

You can execute Lua scripts from the chat window or in a macro with the /run or /script command. There is no difference between them.

/run print("Hello World!")

To quickly turn scripts like this into an addon, just remove the "/run" part and paste it into https://addon.bool.no/

Creating an AddOn

An addon consists of Lua/XML files and a TOC file. We won't be using XML since most things that are possible in XML can also be done in Lua.

Go to your AddOns folder and create a new folder: World of Warcraft\_retail_\Interface\AddOns\HelloWorld

HelloWorld.lua

print("Hello World!")

HelloWorld.toc

## Interface: 90100
## Version: 1.0.0
## Title: Hello World
## Notes: My first addon
## Author: YourName

HelloWorld.lua
  • The name of the TOC file must match the folder name or the addon won't be detected by the game.
  • The TOC Interface metadata 90100 as returned by GetBuildInfo() tells which version of the game the addon was made for. If they don't match then the addon will be marked out-of-date in the addon list.

Load up World of Warcraft, the addon should show up in the addon list and greet you upon login.

Development tips

See also: Lua Coding Tips

  • When updating addon code use /reload to test the new changes, you may want to put it on a macro hotkey; as well as temporarily disabling any unnecessary addons that would increase loading time.
  • Get an error reporting addon like BugSack or turn on /console scriptErrors 1
  • There is the /dump slash command for general debugging, /etrace for showing events and /fstack for debugging visible UI elements.
  • Export, clone, download or bookmark Blizzard's user interface code a.k.a. the FrameXML. If you don't know what a specific API does it's best to just reference it in FrameXML. Not everything is documented so we generally look through the code from Blizzard or other addons.
  • For VS Code the Lua extension by Sumneko adds IntelliSense features like code completion.

Responding to events

Main article: Handling events

Almost every action in the game is an Event which tells the UI that something happened. For example CHAT_MSG_CHANNEL fires when someone sends a message in a chat channel like General and Trade.

To respond to events you create a frame with CreateFrame() and register the events to it.

Event payload in the chat window and /etrace

local function OnEvent(self, event, ...)
	print(event, ...)
end

local f = CreateFrame("Frame")
f:RegisterEvent("CHAT_MSG_CHANNEL")
f:SetScript("OnEvent", OnEvent)

Another example, to play a sound on levelup with PlaySoundFile() you register for the PLAYER_LEVEL_UP event. You can get a sound file from e.g. here, rename it to johncena.mp3 and put it in your addon's folder. You can /reload instead of restarting the game client when adding a sound file.

local f = CreateFrame("Frame")
f:RegisterEvent("PLAYER_LEVEL_UP")
f:SetScript("OnEvent", function()
	PlaySoundFile("Interface/AddOns/HelloWorld/johncena.mp3")
end)

Handling multiple events

When registering multiple events, consider dispatching them to their own function.

local function OnEvent(self, event, ...)
	if event == "ADDON_LOADED" then
		local addOnName = ...
		print(event, addOnName)
	elseif event == "PLAYER_ENTERING_WORLD" then
		local isLogin, isReload = ...
		print(event, isLogin, isReload)
	elseif event == "CHAT_MSG_CHANNEL" then
		local text, playerName, _, channelName = ...
		print(event, text, playerName, channelName)
	end
end

local f = CreateFrame("Frame")
f:RegisterEvent("ADDON_LOADED")
f:RegisterEvent("PLAYER_ENTERING_WORLD")
f:RegisterEvent("CHAT_MSG_CHANNEL")
f:SetScript("OnEvent", OnEvent)
local f = CreateFrame("Frame")

function f:OnEvent(event, ...)
	self[event](self, event, ...)
end

function f:ADDON_LOADED(event, addOnName)
	print(event, addOnName)
end

function f:PLAYER_ENTERING_WORLD(event, isLogin, isReload)
	print(event, isLogin, isReload)
end

function f:CHAT_MSG_CHANNEL(event, text, playerName, _, channelName)
	print(event, text, playerName, channelName)
end

f:RegisterEvent("ADDON_LOADED")
f:RegisterEvent("PLAYER_ENTERING_WORLD")
f:RegisterEvent("CHAT_MSG_CHANNEL")
f:SetScript("OnEvent", f.OnEvent)

Slash commands

Main article: Creating a slash command

Slash commands are an easy way to let users interact with your addon. Any SLASH_* globals will automatically be registered as a slash command.

Using a slash command

-- increment the index for each slash command
SLASH_HELLOW1 = "/helloworld"
SLASH_HELLOW2 = "/hw"

-- define the corresponding slash command handler
SlashCmdList.HELLOW = function(msg, editBox)
	local name1, name2 = strsplit(" ", msg)
	if #name1 > 0 then -- check for empty string
		print(format("hello %s and also %s", name1, name2 or "Carol"))
	else
		print("Please give at least one name")
	end
end

We can also add a shorter /reload command.

SLASH_NEWRELOAD1 = "/rl"
SlashCmdList.NEWRELOAD = ReloadUI

SavedVariables

Main article: Saving variables between game sessions

To store data or save user settings, set the SavedVariables in the TOC which will persist between sessions. You can /reload instead of restarting the game client when updating the TOC file.

## Interface: 90100
## Version: 1.0.0
## Title: Hello World
## Notes: My first addon
## Author: YourName
## SavedVariables: HelloWorldDB

HelloWorld.lua

SavedVariables are only accessible once the respective ADDON_LOADED event fires. This prints how many times you logged in (or reloaded) with the addon enabled.

local function OnEvent(self, event, addOnName)
	if addOnName == "HelloWorld" then -- name as used in the folder name and TOC file name
		HelloWorldDB = HelloWorldDB or {} -- initialize it to a table if this is the first time
		HelloWorldDB.sessions = (HelloWorldDB.sessions or 0) + 1
		print("You loaded this addon "..HelloWorldDB.sessions.." times")	
	end
end

local f = CreateFrame("Frame")
f:RegisterEvent("ADDON_LOADED")
f:SetScript("OnEvent", OnEvent)

This example initializes the SavedVariables with default values. It also updates the DB when new keys are added to the defaults table.
The CopyTable() function is defined in FrameXML. GetBuildInfo() is an API function. Depending on the addon consider using AceDB for managing SavedVariables.

local db
local defaults = {
	sessions = 0,
	someOption = true,
}

local function OnEvent(self, event, addOnName)
	if addOnName == "HelloWorld" then
		-- makes a copy instead of pointing HelloWorldDB to defaults
		-- since tables are passed as a reference
		HelloWorldDB = HelloWorldDB or CopyTable(defaults)
		-- add any new options from defaults
		for k, v in pairs(defaults) do
			if HelloWorldDB[k] == nil then -- don't reset any false values
				HelloWorldDB[k] = v
			end
		end
		db = HelloWorldDB -- use a local variable which points to the DB for readability

		db.sessions = db.sessions + 1
		print("You loaded this addon "..db.sessions.." times")
		print("someOption is", db.someOption)

		local version, build, _, tocversion = GetBuildInfo()
		print(format("The current WoW build is %s (%d) and TOC is %d", version, build, tocversion))

		self:UnregisterEvent(event) -- skip checking for any other addons
	end
end

local f = CreateFrame("Frame")
f:RegisterEvent("ADDON_LOADED")
f:SetScript("OnEvent", OnEvent)

SLASH_HELLOW1 = "/helloworld"
SLASH_HELLOW2 = "/hw"

SlashCmdList.HELLOW = function(msg, editBox)
	if msg == "reset" then
		HelloWorldDB = CopyTable(defaults) -- reset to defaults
		db = HelloWorldDB -- update our table reference
		print("DB has been reset to default")
	elseif msg == "toggle" then
		db.someOption = not db.someOption
		print("Toggled someOption to", db.someOption)
	end
end

Tip: use /dump HelloWorldDB or /tinspect HelloWorldDB to show the contents of a global table.

AddOn namespace

Main article: Using the AddOn namespace

The addon namespace is a private table shared between Lua files in the same addon. This way you can avoid leaking variables to the global environment.

## Interface: 90100
## Version: 1.0.0
## Title: Hello World

FileA.lua
FileB.lua

FileA.lua

local _, addonTbl = ...
addonTbl.foo = "Friends"

FileB.lua

local addonName, addonTbl = ...
print(addonName, addonTbl.foo) -- prints "HelloWorld" and "Friends"

Or you can use a unique global variable between your files.

MyCoolAddon = {}
MyCoolAddon.value = 0

function MyCoolAddon:DoSomething()
	self.value = self.value + 1
end

Either way, it's a good idea to modularize the addon if it grows too big.

Conclusion

You should now understand how to write a simple addon! To share it with other players zip up the folder and publish it on CurseForge (guide) and/or WoWInterface (guide).

For automatically packaging and pushing updates see Using the BigWigs Packager with GitHub Actions.

See also

ElinkIcon-youtube.png Mayron: Creating WoW AddOns
ElinkIcon-youtube.png Paul Halliday: World of Warcraft AddOn Development