The package manager is part of the wowlua binary. It resolves, fetches,
locks and embeds dependencies, and manages workspaces. Publishing is covered
in Publishing, and the manifest format in Manifest.
10.1 Packages
A package is a directory with a wowlua.json. Its kind determines how it
is built and consumed:
| Kind | Produces | Consumed as |
|---|---|---|
addon |
An installable AddOn folder | Not a dependency. Other addons reference it at runtime through addon-dependencies. |
library |
wowlua modules | Compiled into each dependent addon (private copy, tree-shaken) |
lua-library |
Plain Lua/XML files (e.g. LibStub-based libraries) | Copied into each dependent addon under <libs-dir>/<install-path>/ (default libs/<name>/), loaded in their declared order |
types |
.d.wowlua declarations only |
Used by the type checker. Never emitted. |
Plain Lua libraries without a wowlua.json (most existing WoW libraries)
can be used directly: the dependency declaration supplies kind: "lua-library" and, if needed, the file list (§10.5).
10.2 Dependency kinds
| Manifest section | Meaning |
|---|---|
dependencies |
Libraries embedded into the build output |
dev-dependencies |
Only for checking, testing and tooling: type packages, mocks, test helpers. Never embedded. |
addon-dependencies |
Other AddOns that must or may be installed in the game. Written to the TOC. Never fetched or embedded. |
10.3 Sources
"dependencies": { "libstub": { "git": "https://github.com/example/LibStub", "tag": "1.0.3", "kind": "lua-library" }, "ace3": { "git": "https://github.com/WoWUIDev/Ace3", "rev": "8d2a1c0", "kind": "lua-library", "files": ["AceAddon-3.0/AceAddon-3.0.xml", "AceEvent-3.0/AceEvent-3.0.xml"] }, "shared-utils": { "path": "../shared-utils" }, "libdeflate": { "archive": "https://example.com/LibDeflate-1.0.2.zip", "sha256": "9f2c…", "kind": "lua-library", "strip-prefix": "LibDeflate/" }, "wowlua-ui": "^0.4.0"}| Source | Fields | Notes |
|---|---|---|
| Registry | "<semver req>" or { "version": "…", "registry": "…" } |
Default registry: https://registry.wowlua.dev (§10.10). Not available until the registry exists: error E4102 (decision T104). |
| Git | git, plus one of tag, branch, rev; optional subdir, submodules (default false) |
Locked to a commit hash. Uses the system git, so credentials and SSH work as usual. Submodules are only fetched with "submodules": true; their commits are fixed by the locked parent commit. "tag": "latest" names the newest tag among the last 50 commits of the default branch, re-resolved by wowlua update (decision T149). |
| Path | path |
Relative to the manifest. Never locked by checksum. For workspaces and local development. |
| Archive | archive (URL to .zip / .tar.gz; https://, or file:// for a local archive, decision T108), sha256 (required, of the archive file), optional strip-prefix |
For release downloads. Without strip-prefix, an archive holding a single folder (a GitHub archive) has that folder as its root (decision T152). |
| SVN | svn (repository URL, may include a path such as …/trunk/AceAddon-3.0), optional rev, optional submodules (default false: SVN externals are not fetched) |
Locked to a revision number: the explicit rev, otherwise the last revision that changed the URL (decision T105). Uses the system svn client. Many WoW libraries (Ace3, LibStub, CallbackHandler, LibSharedMedia on repos.curseforge.com) are only published this way (decision T51). |
Common fields:
| Field | Meaning |
|---|---|
kind |
Overrides or supplies the package kind (required for sources without a wowlua.json) |
optional |
Only included when a feature enables it (Manifest §features) |
flavors |
Only included for these flavors |
bundle |
false keeps the library as separate files even in bundled builds |
files |
lua-library only: explicit ordered list of .lua / .xml / .toc entry files |
load |
false: a lua-library shipped without entry files, not loaded (a converted project's external nothing loads, decision T156) |
moved-up |
The trailing folders of subdir / strip-prefix a .pkgmeta move-folders moved up: in a converted project the other files of the folder above ship next to them, as the packager leaves them (decision T152) |
types |
A types package or a path to .d.wowlua files describing this library |
install-path |
Folder under build.libs-dir (default: the dependency key) |
features, default-features |
Features of the dependency to enable (Manifest §9.5) |
curseforge-slug |
Slug used for CurseForge relations |
10.4 Versions and resolution
- Versions follow SemVer 2.0. Requirement syntax is Cargo's:
^1.2(the default for bare versions),~1.2.3,=1.2.3,>=1.0, <2.0,*. - Resolution covers the union. The resolver resolves all members of a
workspace together, over the union of all features, all flavors and all
dev-dependencies. Enabling features, building another flavor, or running tests therefore never changes the lockfile. - One version per package per addon. Every dependency is embedded into a
single AddOn, so each addon, across all of its flavors, uses exactly one
version of each package. Across a workspace, the resolver prefers one version
for everyone. Addons with incompatible requirements may end up on different
versions; lock entries record which addons use them (
used-by). Conflicting requirements within one addon are error E4101, explained by the resolver's derivation tree.dev-dependenciesare resolved but never embedded. - Identity. A package is identified by its name and source. The same name coming from two different sources in one addon's graph (e.g. git and registry) is error E4321.
- The resolver uses the PubGrub algorithm (
pubgrubcrate) and prefers the highest compatible version, or the locked version if one exists and still satisfies all requirements. Yanked versions are never chosen by a new resolution. A locked yanked version keeps working with warning W4322. - Git and archive dependencies without a
wowlua.jsonhave version0.0.0+<commit or sha>. With a manifest, they have its version.
10.5 Embedding
10.5.1 wowlua libraries
Library modules are compiled together with the addon:
- Module IDs are prefixed with the package name (
wowlua-ui/widgets/button). - Only modules reachable from the addon's imports are included (tree-shaking at module granularity, plus dead-code removal inside modules when enabled).
- Every addon gets its own private copy in its addon table. Two addons using different versions of the same library never interfere.
- The library is type-checked against its own manifest's check settings. The consumer sees its exported types.
wowlua:metainside a library.addon,buildandflavordescribe the consuming addon's build.featuresandconfigare the library's own: its features are enabled through the consumer's dependency entry (features,default-features), and itsconfigdefaults (the library manifest's top-levelconfig) can be overridden by the dependency entry'sconfigobject.- Flavors. A library supports all flavors unless its manifest lists
flavors. Depending on a library for a flavor it does not support is error E4327, unless the dependency entry restricts itself withflavors. - Output. Modules are emitted to
<libs-dir>/<install-path>/(defaultlibs/<name>/), mirroring the library'sbuild.src, and load in the order the load-order analysis computes for them, like the package's own modules. - Dependency code (decision T111). Warnings and lints in library code are not reported to the consumer (errors are), and library code is never instrumented, monitored or runtime-checked by the consumer's profile.
- Runtime meta (decision T110). At run time,
wowlua:meta'sconfigandfeaturestables are the consuming addon's. A library may therefore only use values that fold to constants (scalars); reading a table-valued path at run time is an error. - Checking a library (decision T112).
wowlua checkin a library package checks it on its own, for all its flavors.wowlua buildrefuses: a library is only built into the addons that depend on it.
types packages contribute their declarations (types.declarations,
default every .d.wowlua) to the checker, from dependencies and
dev-dependencies. A dependency entry's types may also name a path (a
.d.wowlua file or a folder of them) relative to the manifest.
10.5.2 Plain Lua libraries
lua-library dependencies are placed in <libs-dir>/<install-path>/
(default libs/<dependency key>/), keeping their relative layout. The files to
load, in order, are determined by the first of these that applies:
- the
filesfield of the dependency declaration (entries may be.lua,.xml, or a.tocwhose file list is used); - the
lua-library.filesfield of the library's ownwowlua.json, if present; - a single
.tocfile in the library root (its file list, ignoring## Interfaceand metadata). Flavor-suffixed TOCs are matched to the build flavor; - a single
lib.xml,embeds.xmlor<name>.xmlin the library root.
Otherwise the dependency is error E4110 and asks for files. Only the entry
files appear in the TOC. Entry paths are relative to the library root and
resolve case-insensitively, like the client.
Load position (decision T106). Entry files load before the package's own
files: dependencies of a library before the library, otherwise in the
manifest's declaration order. build.load-order may name an entry file by its
output path (libs/LibStub/LibStub.lua) to place it among the package's files;
wowlua init --from-pkgmeta writes such entries, so migrated addons keep
their exact order.
The package's own XML (decision T107). A reference in one of the package's
XML files that resolves into <libs-dir>/<install-path>/ (relative to the
source folder, as an embeds.xml written for the packager does) names that
library's file. It is rewritten to the absolute in-game path
(Interface\AddOns\<Name>\Libs\LibStub\LibStub.lua), the file is copied with
everything it reaches, and it is not listed in the TOC again.
Copied files. Only what the addon can load is copied: the entry files,
every file reachable from them through XML <Script> / <Include> chains
(which must resolve), and the library's license files (LICENSE*, COPYING*,
or lua-library.license-files). Tests, docs and other files of the library
repository are not copied. A converted packager project
(compat.packager-keywords) ships every file of the library instead, like the
packager ships externals: all but dotfiles, its repository's .pkgmeta
ignore and the project's build.exclude (decision T144).
These libraries keep their own runtime versioning (LibStub). wowlua does not
rename or rewrite them, except when the minification level applies to bundled
libraries (minify-libraries, default false). License header comments are
always preserved (Code generation §14.8.6).
10.5.3 Nested dependencies
Dependencies of dependencies are embedded as well, deduplicated per addon. A
lua-library that itself embeds a copy of a library (e.g. a bundled LibStub)
is left untouched. LibStub resolves duplicates at runtime.
10.6 Lockfile
wowlua.lock is generated JSON. It is committed for addons and ignored for
libraries.
{ "version": 1, "flavor-table": { "version": "2026.10.11", "interfaces": { "standard": [120100], "mists": [50504], "vanilla": [11509] } }, "packages": [ { "name": "ace3", "version": "0.0.0+8d2a1c0e9f…", "source": "git+https://github.com/WoWUIDev/Ace3?rev=8d2a1c0#8d2a1c0e9f…", "checksum": "sha256-3b4f…", "dependencies": [], "used-by": ["my-addon"] }, { "name": "wow-api-standard", "version": "12.1.0+gen.3", "source": "registry+https://registry.wowlua.dev", "checksum": "sha256-77e0…", "dependencies": [], "used-by": ["my-addon"] } ]}- Flavor table. The lockfile pins the flavor-table version and the
resolved interface numbers of every targeted flavor, so every machine
generates identical TOCs.
wowlua types syncupdates them deliberately; the change shows up in the lockfile diff. - Checksums cover the normalized file tree of the package (sorted paths, contents, executable bit ignored) and are verified on every fetch (E4103).
--lockedfails if the lockfile would change (E4104).--offlineuses only the cache: resolution may still run and update the lockfile, but nothing is downloaded (E4105 if something is missing).--frozenmeans--lockedplus--offline.--lockedand--frozenare meant for CI.- Packages are sorted by name, and keys are written in a fixed order, so diffs stay minimal.
- Lockfile versions: see Manifest §9.16.
10.7 Cache
| Path | Content |
|---|---|
$WOWLUA_HOME (default ~/.wowlua) |
Root |
…/cache/git/<name>-<hash>/ |
Bare clones |
…/cache/checkouts/<kind>-<name>-<hash>-<pin>/ |
Exported trees: one per git commit, SVN revision; without version-control metadata |
…/cache/archives/<sha256>, …/cache/archives/<sha256>.d/ |
Downloaded archives and their extracted trees |
…/cache/registry/<registry>/<name>/<version>/ |
Unpacked registry packages and index cache |
…/cache/compile/<hash> |
Lua 5.1 compile results (see below) |
…/maps/<build id>/ |
Archived source maps of published builds (Code generation §14.6) |
…/config.json |
Global configuration: WoW install paths, registries (see CLI). Tokens are not stored here unless the OS keychain is unavailable (§10.11). |
wowlua cache clean removes entries that no fetch or build has used for 30
days; every use stamps the entry (<entry>.used). --all removes everything
(decision T109). All cache writes are atomic
(write to a temp location, then rename), so concurrent builds are safe.
Compile results. Builds compile Lua with the embedded Lua 5.1 to report
its limits (E5001) and to choose between the optimizer's variants. The
result depends only on the bytes, the chunk name and the Lua build, so the
CLI keeps it in …/cache/compile/<hash> (a blake3 hash of the three): a
later build of the same bytes takes it from there, and only that build's
new or changed files are compiled. An entry is created once and records
its length; one cut short is a miss and is written again, so concurrent
builds are safe. An entry's modification time is its last use: when the
cache grows past build.compile-cache-size (MiB, default 256), the least
recently used entries are removed, at most once a day, until it is back
under three quarters of the limit. cache clean removes entries unused for
30 days, --all all of them. WOWLUA_NO_COMPILE_CACHE=1 or
build.compile-cache: false turns the cache off (CLI
§12.7; decision T208). Debug builds of wowlua compile anyway and
check that the two results agree.
10.8 Workspaces
A workspace groups several packages, typically an addon suite such as a core addon plus load-on-demand modules, together with shared libraries.
{ "workspace": { "members": ["addons/*", "libs/*"], "default-members": ["addons/core"] }}- One
wowlua.lockand onedist/at the workspace root. - Members are the folders with a
wowlua.jsonthatmembersmatches (andexcludedoes not), plus the root itself when its manifest has apackagesection. - Selection (decision T113).
-p <package>selects one member. Otherwise a command run inside a member's folder works on that member, and one run at the root works ondefault-members(all members if it is empty).build,check,install,uninstall,clean,fmt,fetch,update,treeandoutdatedwork on every selected member;buildandinstallskip library members, whichcheckchecks. Commands on one package (add,remove,toc,lint,fix,dev,trace) need exactly one selected member. - An addon may list another workspace addon in
addon-dependencies. The compiler then type-checks against that addon's generated declaration file, which describes its@globalAPI:@globalfunctions with their signatures, values with their annotated or literal type, and classes and enums asany(decision T114). The file is generated from the other addon's collected declarations, not from a full build (Compiler §13.3.1, stage 12), so two addons that list each other as optional dependencies still build. - A cycle of
requiredaddon dependencies within the workspace is error E4324. Two members with the sameaddon.nameare error E4325. - SavedVariables names must be unique across the workspace (E4120).
wowlua packagecan produce one zip containing several AddOn folders (publish.bundle-addons).
10.9 Types packages
The WoW API is typed by pre-generated types packages, one per flavor:
| Package | Content |
|---|---|
wow-api-<flavor> (e.g. wow-api-standard, wow-api-mists) |
Global API functions, C_* namespaces, enums, structures, events with typed payloads, widget classes, FrameXML globals and mixins, and WoW's Lua environment additions, as .d.wowlua |
Generation. The packages are produced in the wowlua project's CI by
wowlua-types-gen:
- Primary source: Blizzard's API documentation
(
Blizzard_APIDocumentationGenerated), read per flavor from the publicwow-ui-sourcemirror branches (live,ptr,classic,classic_era,classic_anniversary,classic_titan,forever, …). It provides functions withNilable/Defaultparameters, multiple returns, structures, enums, events with payloads, and secret-value annotations (→Secret<T>, see Types §2.11). It is loaded by executing the documentation tables in a sandboxed Lua VM, as existing community tools do. - Enrichment: Ketho's vscode-wow-api annotations (MIT). They are
converted from LuaLS
---@annotations and merged where they add information that Blizzard's documentation lacks: widget class hierarchies and methods, hand-written structures and string-literal unions (unit tokens, frame types, templates), FrameXML globals and mixins, documentation links, and deprecations. On conflict, Blizzard's data wins for signatures, and the enrichment wins only where Blizzard's data is missing or untyped (any). - Flavor availability from per-flavor global name dumps, used to verify that every declared global exists in that flavor.
Package versions follow the game version: wow-api-standard@12.1.0 for build
12.1.0, and pre-releases for test clients (12.1.5-ptr.70077). Generator fixes
are released as +gen.N build metadata. SemVer ignores build metadata for
precedence; for types packages only, wowlua deliberately deviates and orders
versions that are otherwise equal by N, numerically, so the newest generator
output wins. The packages are published to the wowlua registry and as GitHub
release assets of the wowlua project, and include the MIT notice of the
enrichment source.
Use. For every flavor in the manifest, the matching wow-api-<flavor>
package is an implicit dev-dependency with the requirement =X.Y.Z, derived
from the flavor's pinned interface number (120100 → =12.1.0), using the
newest +gen.N. It can be pinned in dev-dependencies or per flavor
(flavors.<id>.types). wowlua types sync updates the flavor table and the
locked type package versions after a game patch.
Converter. wowlua-types-gen is also exposed as wowlua types convert <path> and converts any LuaLS-annotated library (e.g. a library's ---@meta
files) into .d.wowlua. Supported LuaLS tags: @meta, @class (with multiple
inheritance, which becomes an intersection for non-primary parents), @field, @param,
@return, @overload, @alias (including ---| literal unions), @enum,
@generic (including backtick captures, which become literal-type generics),
@type, @deprecated, plus the type syntax fun(...), table<K,V>, T[],
tuples, literals and dotted names. Other tags are ignored.
10.10 Registry
The wowlua registry hosts library, lua-library and types packages.
Addons are distributed through addon sites instead. It is a later
milestone (Roadmap). The protocol is specified now so that
clients and servers can be built independently.
10.10.1 Index (sparse HTTP)
GET {registry}/config.jsonreturns{ "api": "<api base>", "dl": "<download base>" }.GET {registry}/index/<name>returns newline-delimited JSON, one line per published version:{"name":"wowlua-ui","vers":"0.4.2","deps":[{"name":"libstub","req":"^1.0","kind":"normal"}],"cksum":"sha256-…","features":{},"yanked":false,"wowlua":">=0.3"}- Index files are cacheable and support
ETag/If-None-Match.
10.10.2 Download and publish
All registry traffic uses HTTPS. Authenticated requests send
Authorization: Bearer <token>.
| Request | Meaning | Success |
|---|---|---|
GET {dl}/<name>/<version>.tar.gz |
Download a package archive | 200 |
PUT {api}/v1/packages/<name>/<version> |
Publish. multipart/form-data with manifest (the package's wowlua.json, normalized) and archive (the .tar.gz). The server checks that name and version match the manifest and computes the checksum. |
201 {"name":…,"vers":…,"cksum":…} |
DELETE {api}/v1/packages/<name>/<version> |
Yank: the version stays downloadable for existing lockfiles but is never chosen by new resolutions | 200 |
PUT {api}/v1/packages/<name>/<version>/unyank |
Undo a yank | 200 |
GET / PUT / DELETE {api}/v1/packages/<name>/owners |
List, add, remove owners (body: {"users": [...]}) |
200 |
Errors use HTTP status 400 (invalid package), 401 (missing or bad token), 403
(not an owner), 404 (unknown package), 409 (version already exists) and 413
(archive too large), with the body
{"errors": [{"code": "<machine code>", "detail": "<message>"}]}.
- Versions are immutable. A published version can be yanked, never replaced.
- Package names: lowercase
[a-z0-9-], 2–64 characters, first come first served. Names of well-known WoW libraries are reserved for their authors.
10.10.3 Package archive
wowlua package --registry (or wowlua publish --target registry) creates a
.tar.gz containing wowlua.json, sources, .d.wowlua files and license
files, but no dist/, tests or dev files.
10.11 Security
- No code runs at fetch or build time. Packages have no build scripts,
install hooks or plugins. Dependency Lua is only compiled or copied. It runs
in the game, or in the sandboxed test VM (no
io,os,loadfile,dofile,require). - Archive and checkout safety. Extraction rejects absolute paths, paths
containing
.., and symlinks or hardlinks that point outside the package (E4323). Git submodules are only fetched when opted in (§10.3). - Integrity. Checksums are verified on every fetch (E4103). Registry and archive downloads require HTTPS.
- Tokens.
wowlua login <type>stores tokens in the OS credential store (macOS Keychain, Windows Credential Manager, Secret Service on Linux; servicewowlua, account = the site type);wowlua logout <type>removes them. Environment variables take precedence. Only when no credential store is available are tokens written toconfig.json, created with permissions0600, with warning W4326. Tokens are redacted in all output, including-vand--dry-run.