Getting Started
Installation
VS Code
Install the wowlua-ls extension from the VS Code marketplace. It bundles the language server binary — no separate install needed.
JetBrains IDEs
Download the plugin ZIP for your platform from GitHub Releases and install via Settings → Plugins → ⚙️ → Install Plugin from Disk.... Requires the LSP4IJ plugin. The release ZIPs bundle the language server binary — no separate install needed.
Neovim
Neovim has built-in LSP client support — no plugin required, just configuration.
1. Get the binary. Either download a release from GitHub Releases, or build from source:
git clone https://github.com/TradeSkillMaster/wowlua-ls.git
cd wowlua-ls
cargo build --release
# Binary is at target/release/wowlua_ls2. Configure the LSP client. Add this to your Neovim config (requires Neovim 0.11+):
vim.lsp.config('wowlua_ls', {
cmd = { '/path/to/wowlua_ls' },
filetypes = { 'lua' },
root_markers = { '.wowluarc.json', '.toc', '.git' },
workspace_required = false,
})
vim.lsp.enable('wowlua_ls')Replace /path/to/wowlua_ls with the actual path to the binary. workspace_required = false lets the server attach to standalone Lua files outside a project.
LazyVim
If you use LazyVim, add a plugin spec:
-- ~/.config/nvim/lua/plugins/wowlua-ls.lua
return {
{
"neovim/nvim-lspconfig",
opts = {
servers = {
wowlua_ls = {
cmd = { "/path/to/wowlua_ls" },
filetypes = { "lua" },
root_markers = { ".wowluarc.json", ".toc", ".git" },
workspace_required = false,
},
},
},
},
}nvim-lspconfig (standalone)
If you use nvim-lspconfig without a distro, you can register the server manually:
local configs = require('lspconfig.configs')
if not configs.wowlua_ls then
configs.wowlua_ls = {
default_config = {
cmd = { '/path/to/wowlua_ls' },
filetypes = { 'lua' },
root_dir = require('lspconfig.util').root_pattern('.wowluarc.json', '.toc', '.git'),
single_file_support = true,
},
}
end
require('lspconfig').wowlua_ls.setup({})TIP
If you also use a general-purpose Lua language server (e.g. lua_ls), you may want to disable it for WoW addon projects to avoid duplicate diagnostics. You can do this by checking for .wowluarc.json in your lua_ls config's root_dir or using a filetype autocmd.
Other editors
Build the language server from source:
git clone https://github.com/TradeSkillMaster/wowlua-ls.git
cd wowlua-ls
cargo build --releaseThe binary is at target/release/wowlua_ls. Configure your editor to run it as an LSP server over stdio for Lua and TOC files.
Your first project
Open a WoW addon folder in your editor. wowlua-ls will automatically:
- Scan all
.lua,.xml, and.tocfiles in the workspace - Load the built-in WoW API stubs (retail + classic)
- Resolve cross-file classes, globals, and addon namespaces
- Extract frame and template types from XML files
- Provide interactive editing support for TOC files
- Start reporting diagnostics
No configuration file is required to get started. The defaults are sensible for most addons.
Adding a configuration file
For project-specific settings, create a .wowluarc.json in your addon's root directory:
{
"ignore": ["Libs/"],
"flavors": ["retail", "classic"],
"diagnostics": {
"enable": ["need-check-nil"]
}
}This tells wowlua-ls:
- Skip
Libs/— don't analyze third-party library code - Target retail and classic — warn about flavor-specific APIs
- Enable nil checking — report
need-check-nilwarnings (off by default)
See Configuration for the full reference.
Adding annotations
wowlua-ls infers a lot on its own, but annotations make it smarter. Start with the high-value ones:
Annotate your public API
---@param itemId number
---@param count number?
---@return boolean success
---@return string? error
function MyAddon:BuyItem(itemId, count)
if not self.store then
return false, "store not initialized"
end
-- ...
return true
endThe LS now knows the parameter types, that count is optional, and that the function returns a boolean plus an optional error string. Every caller gets type checking and hover information.
Annotate your classes
---@class AuctionEntry
---@field itemId number
---@field buyout number
---@field seller string
---@field duration number?Now AuctionEntry is a named type. You can reference it in @param and @return annotations across your codebase, and the LS will provide completion and type checking for its fields.
When to use @type
@type forces a variable's type. It's most useful when the LS can't infer what you need. For cases where the LS already has the answer, it's up to you — the annotation won't hurt, but you can save yourself the effort:
-- Useful: tells the LS what this empty table will hold
---@type AuctionEntry[]
local entries = {}
-- Useful: the LS can't infer the type from nil alone
---@type Frame?
local cachedFrame = nil
-- Optional: the LS already infers this from the assignment
---@type number
local x = 5What to annotate next
Once you've annotated your core classes and public functions, the LS handles most of the rest through inference. When you see a ? type on hover (meaning the LS couldn't figure it out), that's a signal to add an annotation.
