Create an addon, run it in the game while you edit it, and test it outside the game. This takes a few minutes. You need wowlua installed and, for the in-game part, a WoW client on the same machine.
Create an addon
wowlua new MyAddoncd MyAddon Created addon package in MyAddon--kind picks the template (addon, library, lua-library or types),
--flavors the game flavors to build for (default standard, the retail
client), and --ci github adds a GitHub Actions workflow that builds pushes
to main and pull requests, and publishes v* tags. When git is available and the folder is not inside
a repository yet, new also runs git init.
What wowlua new creates
MyAddon/├── wowlua.json manifest: addon metadata, flavors, dependencies, publish targets├── src/│ └── main.wowlua the addon's code├── tests/│ └── main.test.wowlua tests, never part of a build├── locales/│ └── enUS.json translations for wowlua:locale├── CHANGELOG.md├── README.md└── .gitignore ignores dist/The first build adds wowlua.lock, which pins the interface numbers and
dependency versions. Commit it. Builds go to dist/, which is generated:
dist/dev/MyAddon/ holds one .lua file per source with line numbers
preserved, and dist/release/MyAddon/ the bundled release build.
The manifest starts small:
{ "$schema": "https://raw.githubusercontent.com/DomenikIrrgang/wowlua/main/schema/wowlua-0.1.json", "package": { "name": "my-addon", "version": "0.1.0", "authors": [ "Your Name" ], "wowlua": "^0.9" }, "addon": { "title": "MyAddon" }}authors comes from your git user.name. There is no TOC file to edit:
wowlua generates one for every flavor from wowlua.json. The
manifest reference lists every key.
Run it in the game
wowlua dev Built MyAddon 0.1.0-dev (dev: standard) in 0.06s Installed /Applications/World of Warcraft/_retail_/Interface/AddOns/MyAddon Ready type /reload in game to load the changeswowlua dev builds with the dev profile and copies the AddOn into every
client folder of your WoW installation that matches a built flavor
(_retail_, _ptr_, _classic_era_, …). Then it watches your files. Each
time you save, it builds again, compiling only what changed, and syncs the
result.
- It finds the installation from
--wow <path>, theWOWLUA_WOW_PATHenvironment variable,wow.pathin~/.wowlua/config.json, or the Battle.net default folder on macOS and Windows. On Linux, set one of the first three. - It never overwrites an AddOn folder it did not install, such as the same
addon from an addon manager. That is error E4331, unless you pass
--force. - A failed build leaves the installed AddOn alone, so the last working version stays in the game.
--linklinks the AddOn folder to the build folder instead of copying it.wowlua installbuilds once and installs, andwowlua uninstallremoves what wowlua installed.
Log in, or type /reload if the game is already running. WoW only loads Lua
again on /reload, so reload after each change.
The details are in CLI §12.5.
Your first edit
Replace src/main.wowlua with a module that prints a line in the chat when the
player logs in:
import addon, build from "wowlua:meta"import event from "wowlua:events"import print as chat from "wowlua:log" @event("PLAYER_LOGIN")local function onLogin() chat(`{addon.title} {addon.version} loaded ({build.profile} build)`)endSave, then /reload in the game. The chat shows MyAddon 0.1.0 loaded (dev build) after the addon's colored name.
wowlua:meta holds the addon's metadata and build information, folded into
the output at compile time. @event registers the function for the event.
print from wowlua:log writes to the chat frame with the addon's name in
front. The dev build keeps each line where it was, so an in-game error in
src/main.lua line 7 is line 7 of src/main.wowlua:
local __wl = select(2, ...).__wowlualocal __wl_m1 = __wl.require("wowlua:events") local event = __wl_m1.eventlocal __wl_m2 = __wl.require("wowlua:log") local chat = __wl_m2.print local functiononLogin() chat("MyAddon 0.1.0 loaded (dev build)")end onLogin = __wl.prof.wrap(onLogin, "onLogin", "src/main.wowlua:6") __wl.register(onLogin, {kind = "function", name = "onLogin", static = false, file = "src/main.wowlua", line = 8}, event("PLAYER_LOGIN"))The __wl.prof.wrap call is the dev build's profiler. Release builds have
none of it.
Mistakes before /reload
Make a typo, such as addon.titel. wowlua dev reports it in the terminal
before the game ever loads the file, and so do wowlua build and
wowlua check:
error[E2003]: `wowlua:meta` has no field `addon.titel` ┌─ src/main.wowlua:7:12 │7 │ chat(`{addon.titel} {addon.version} loaded ({build.profile} build)`) │ ^^^^^^^^^^^ here error: aborting due to 1 previous errorwowlua check type-checks without writing a build. wowlua explain E2003
explains a code. All codes are listed in the
diagnostics appendix.
Plain .lua files keep working: every Lua 5.1 file is valid wowlua. In a file
that uses no wowlua syntax, name and type errors are only warnings, so an
existing file never fails the build. Rename files to .wowlua one at a time
when you want types (Types §2.1).
Test it
wowlua test runs the test files in wowlua's emulation of the WoW client: a
Lua 5.1 VM with WoW's Lua, taint and secret values, and the flavor's WoW API.
It loads the whole addon as the client would, fires ADDON_LOADED,
PLAYER_LOGIN and PLAYER_ENTERING_WORLD, then runs the tests. No game
client is needed.
The template's test still expects the template's message, so it fails now:
wowlua test FAIL tests/main.test.wowlua (standard, 0.04s) ✗ greets at login expected { "|cff33ff99MyAddon|r: MyAddon 0.1.0 loaded (test build)" } to contain "MyAddon loaded." at tests/main.test.wowlua:8 printed: |cff33ff99MyAddon|r: MyAddon 0.1.0 loaded (test build)error: tests failed: 0 passed, 1 failed, 0 skipped (1 file) in 0.12sUpdate tests/main.test.wowlua:
import test, expect from "wowlua:test"import * as mock from "wowlua:mock" @test("greets at login")local function greets() local shown = table.concat(mock.printed(), "\n") expect(shown).toContain("MyAddon 0.1.0 loaded")end PASS tests/main.test.wowlua (standard, 0.04s) ✓ greets at login Tested 1 passed, 0 failed, 0 skipped (1 file) in 0.12sEach test file runs once per flavor of the manifest, in a fresh VM.
wowlua:test has the test decorators, hooks and typed matchers. wowlua:mock
drives the simulated game world: units, combat, time, events, user input,
spies and stubs. wowlua test --watch runs again on every change, and
--coverage measures which lines ran. See
Tooling §16.1 for
everything a test can do.
wowlua test is newer than release 0.9.0. Until a release includes it, build
wowlua from source to use it.
Build a release
wowlua build --release Finished MyAddon 0.1.0-alpha (release: standard) in 0.06s → dist/release/MyAddonThe release profile bundles the addon into one file (MyAddon.lua), strips
comments and whitespace, and leaves out the debugging tools. Its version is
0.1.0-alpha until you build on a git tag that matches package.version
(v0.1.0). Source maps go next to the build, outside the AddOn folder, for
wowlua trace.
To publish, add targets under publish.targets in wowlua.json, save a token
with wowlua login <site>, and run wowlua publish. It builds, runs the
tests, packages the zip and uploads it to CurseForge, Wago, WoWInterface and
GitHub Releases. wowlua publish --dry-run shows what each site would get
without uploading anything (Publishing).
Where to go next
- Debugging in game: the dev window and
/wl. - Adopting an addon: bring an existing addon to wowlua.
- The language: syntax, types, classes, interfaces and enums, decorators and modules.
- The standard library: events, saved variables, slash commands, timers, settings, a minimap button and more.
- The sample addons in
examples/, each with tests.