Wax

How a mod is built

The files in a mod, mod.lua, splitting code into files and using another mod.

A mod is a folder. The folder name is the mod's id.

Wax\mods\
    MyMod\
        init.lua      the code that runs
        mod.lua       optional: name, version, dependencies
        helpers.lua   optional: more code, loaded with require

init.lua

Wax runs init.lua from top to bottom when the mod loads. It runs it again, from the top, each time the mod reloads.

MyMod\init.lua
print("My mod is loading.")

local window = ui.Window({ title = "My mod" })
window:Label("Hello.")

mod.lua

mod.lua describes your mod. It is called the manifest. The file is optional, and so is every field in it.

MyMod\mod.lua
return {
    name = "My mod",
    version = "1.2.0",
    main = "init.lua",
    dependencies = { "Greeter" },
}
FieldWhat it meansWhen left out
nameThe name shown on the Mods page.The folder name.
versionThe version shown on the Mods page."0.0.0"
mainThe file Wax runs first."init.lua"
dependenciesIds of mods that must load before this one.None.

mod.lua may only hold plain values like the ones above. It cannot call functions.

What every mod can use

These names are ready in every file of your mod. You don't load them.

NameWhat it is
gameThe root of the game: the world, the player and everything else. See The game tree.
uiWindows, overlays, notifications, hotkeys and themes. See Windows.
taskRun code alongside the game. See Tasks.
SignalMake your own events. See Signals.
print, logWrite to the log. See Printing and the log.
persist, storageKeep values across reloads and across game sessions. See Saving settings.
requireLoad another file of your mod, or another mod.
modFacts about your own mod.
rawEverything UE4SS offers, with no safety checks. For experts.

Lua's own tools (string, table, math, pairs, ipairs, tostring and the rest) work as usual.

The mod table

print(mod.id)       -- the folder name, for example "MyMod"
print(mod.name)     -- the name from mod.lua
print(mod.version)  -- the version from mod.lua
print(mod.dir)      -- the path of the mod's folder

Each mod has its own globals

A global variable you set in one mod is not seen by other mods. Two mods can both use the name settings without clashing.

More than one file

Put shared code in its own file. Load it with require.

MyMod\helpers.lua
local helpers = {}

function helpers.shout(text)
    return text:upper() .. "!"
end

return helpers
MyMod\init.lua
local helpers = require("helpers")

print(helpers.shout("hello"))   -- HELLO!
  • require("helpers") loads helpers.lua.
  • require("tools.math") loads tools\math.lua. If that file is not there, it loads tools\math\init.lua.
  • A file runs once. A second require of the same name gives back the same value.
  • What the file returns is what require gives you.

Saving any .lua file of the mod reloads the whole mod.

Using another mod

One mod can offer functions to others. What its init.lua returns is what other mods get.

Greeter\init.lua
local greeter = {}

function greeter.hello(name)
    return "Hello, " .. name .. "!"
end

return greeter

A mod that uses it does two things. It lists the id under dependencies:

MyMod\mod.lua
return {
    name = "My mod",
    dependencies = { "Greeter" },
}

And it loads the other mod with @ in front of the id:

MyMod\init.lua
local greeter = require("@Greeter")

ui.Notify(greeter.hello("explorer"))

Good to know:

  • Dependencies load first.
  • When Greeter reloads, every mod that depends on it reloads too.
  • If Greeter is missing or switched off, your mod does not load. The Mods page says which mod it needs.
  • require("@Greeter") without listing Greeter under dependencies is an error. The message tells you what to add.

Wax cleans up after you

Everything a mod sets up belongs to that mod: its windows, overlays, hotkeys, tasks and signal connections. When the mod reloads or is switched off, Wax removes all of it. You don't write clean-up code.

Functions that are blocked

A few UE4SS functions are switched off inside Wax mods, because they break the game's Lua state. Calling one raises an error that says why.

BlockedUse this instead
LoopAsync, ExecuteAsync, ExecuteWithDelaytask.spawn, task.delay and task.wait
RegisterKeyBind, RegisterKeyBindAsyncui.Hotkey
RestartMod, RestartCurrentMod, UninstallMod, UninstallCurrentMod, ClearAllDelayedActionsNothing. Wax reloads mods by itself.

The originals are still reachable through raw, for example raw.LoopAsync. They can crash the game.

The full list with reasons is in the reference.

On this page