> For the complete documentation index, see [llms.txt](https://lua2.axeprimecs.com/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://lua2.axeprimecs.com/readme.md).

# Welcome

Welcome to the official **Lua Scripting API** reference.

***

## Editor Setup

Install the **axeprime Language Server** extension for Lua intellisense, autocomplete, and inline API documentation.

| Editor      | Install                                                                                                               |
| ----------- | --------------------------------------------------------------------------------------------------------------------- |
| **VS Code** | [marketplace.visualstudio.com](https://marketplace.visualstudio.com/items?itemName=axeprime.axeprime-language-server) |
| **Cursor**  | [open-vsx.org](https://open-vsx.org/extension/axeprime/axeprime-language-server)                                      |

> Both editors use the same extension from the VS Code Marketplace.

***

## 🚀 Getting Started

### Loading a script

1. Put a `.lua` file into the **scripts** directory.
2. Open the **Lua** tab in the menu, select the script and press **Load**.

Scripts are **not** loaded automatically when the cheat starts. A script can load other scripts with [`scripts.load`](/global-functions/global-functions/core/scripts.md).

Every script runs in its **own Lua state**. Globals are not shared between scripts (use [`net`](/global-functions/global-functions/network/net.md) to pass messages between them).

### Minimal script

```lua
ui.register(function()
    ui.check_box("Show watermark", true)
    ui.slider_int("Offset", 10, 0, 100)
end, "misc")

events.register("paint", function()
    if not ui.get_bool("Show watermark", true) then return end

    local offset = ui.get_int("Offset", 10)
    local sw = render.get_screen_size()
    local text = "axe | " .. globals.user_name()
    local tw = render.text_size(text)

    render.filled_rect(sw - tw - offset - 8, offset, tw + 8, 18, color(0, 0, 0, 160), 4)
    render.text(sw - tw - offset - 4, offset + 2, text, color(255, 255, 255), 0)
end)

function __shutdown()
    print("watermark unloaded")
end
```

### Script lifecycle

1. The file is read and scanned for features that need a [permission](#-permission-system). If one is missing, loading stops and the user is asked.
2. The chunk runs top to bottom. Register your callbacks here.
3. The `script_loaded` event fires (`script_reloaded` when the load came from **Reload all**).
4. Callbacks run until the script is unloaded.
5. On unload the global `__shutdown` function runs (if defined). Timers, particles, GIFs, custom tabs and UI state that belong to the script are discarded.

> `__shutdown` runs only on an explicit unload (Lua tab or `scripts.unload`). It does **not** run on **Reload all**, or when an already loaded script is replaced by loading it again.

### Errors

* An error thrown during load aborts the load.
* An error thrown inside a callback is caught, logged to the Lua error channel of the game console, and the script keeps running. Identical messages are rate limited to three per second.

***

## 🧪 Runtime

Scripts run on **LuaJIT 2.1.0** (Lua 5.1 syntax), bound with sol3.

| Feature         | Details                                                                                              |
| --------------- | ---------------------------------------------------------------------------------------------------- |
| **Lua version** | 5.1                                                                                                  |
| **JIT**         | LuaJIT 2.1.0 — enabled with `hotloop=25`, `hotexit=5` — [Download](http://axeprime.cloud/luajit.rar) |
| **Sandbox**     | Enabled by default; some APIs require permission                                                     |

### Libraries

| Library           | Available              | Notes                                                                                                                                 |
| ----------------- | ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------- |
| base              | yes                    | `print`, `error` and `require` are replaced by axe versions. Includes `coroutine`.                                                    |
| `string`, `table` | yes                    |                                                                                                                                       |
| `math`            | yes                    | Extended by axe, see [Math](/global-functions/global-functions/utils/math.md).                                                        |
| `bit32`           | yes                    | LuaJIT BitOp: `band`, `bor`, `bxor`, `bnot`, `lshift`, `rshift`, `arshift`, `rol`, `ror`, `tobit`, `tohex`, `bswap`. **32-bit only.** |
| `jit`             | yes                    |                                                                                                                                       |
| `package`         | yes                    | Use `require`, not the searchers.                                                                                                     |
| `os`, `io`        | only with **Insecure** |                                                                                                                                       |
| `ffi`             | only with **FFI**      |                                                                                                                                       |
| `debug`, `utf8`   | no                     | Never exposed                                                                                                                         |

> Because `os` is missing by default, use `globals.realtime()` for timers and `utils.get_time()` for the wall clock.

***

## 📂 Script Directories

> **Scripts:** `<game_dir>/bin/win64/axe/scripts/` **Resources:** `<game_dir>/bin/win64/axe/scripts/resources/` **Modules:** `<game_dir>/bin/win64/axe/scripts/lib/` and `<game_dir>/bin/win64/axe/scripts/modules/`

Scripts are placed in the **scripts** directory as `.lua` files.

**Examples:**

* `<game_dir>/bin/win64/axe/scripts/advanced.lua`
* `<game_dir>/bin/win64/axe/scripts/menu.lua`

***

## 📚 Using Modules (`require`)

`require(name)` searches, in order:

1. `scripts/<name>.lua`
2. `scripts/lib/<name>.lua`
3. `scripts/modules/<name>.lua`

Dots map to folders: `require("utils.math")` looks for `utils/math.lua` in each directory.

* The module chunk receives `(module_name, resolved_path)` as `...`.
* It runs in the **same global environment** as the script that required it.
* The result is cached in `package.loaded`. A module that returns nothing makes `require` return `true`.
* Folder modules (`<name>/init.lua`) are **not** supported.

```lua
local UI = require("apex_ui")
-- searches: scripts/apex_ui.lua → scripts/lib/apex_ui.lua → scripts/modules/apex_ui.lua

local targeting = require("ragebot.targeting")
-- searches: scripts/ragebot/targeting.lua → scripts/lib/ragebot/targeting.lua → scripts/modules/ragebot/targeting.lua
```

***

## 🧵 Threads and Callback Context

axe calls scripts from two threads. All callbacks are serialized by one lock, so script code never runs twice at the same time, but game state read on the render thread may be in the middle of an update.

| Thread               | Callbacks                                                                                                                                            |
| -------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Render** (Present) | `paint`, `player_info_pre`, `player_info_post`, `ui.register` callbacks, `delay` / `set_interval`, `http.*_async` callbacks, `net.on` messages       |
| **Game**             | `create_move`, `verify_input`, `frame_stage_notify`, `level_init`, `level_shutdown`, `fire_event_client_side`, `game_event` and per-name game events |

Rules:

* Call `render.*`, `ui.*` and `gui.notify` only from render-thread callbacks.
* Call `panorama.*`, `nade.predict` and `nade.predict_pawn` only from game-thread callbacks (`frame_stage_notify` is available in the main menu too).
* Keep callbacks short. A slow callback on either thread stalls both, because they share the lock.

***

## 🔐 Permission System

Scripts are **sandboxed by default**. Some features are gated, and the check happens twice:

* **At load:** the source is scanned (comments and strings are ignored). If a gated feature is found and the permission is not granted, the script is not loaded and the user is asked. After approval the waiting scripts are loaded again.
* **At call time:** a gated call without permission raises an error (or returns a failure value) and asks the user.

Grants apply to **all** scripts and last until the game is closed.

| Permission      | Unlocks                                                                                                                                                                     | Detected in source by                                                 |
| --------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------- |
| **FFI**         | `ffi` library, [`module` / `pointer`](/global-functions/global-functions/utils/advanced-apis.md)                                                                            | `ffi.`, `ffi:`, `ffi[`, `require("ffi")`                              |
| **Insecure**    | `os`, `io` libraries, `resource_system`, `module` / `pointer`. Also implies File Access, HTTP and Sound.                                                                    | `os.execute`, `os.remove`, `os.rename`, `io.popen`, `package.loadlib` |
| **File Access** | `fs.read`, `fs.read_stream`, `fs.write`, `fs.write_stream`, `fs.remove`, `fs.create_dir`, `zip.open`, archive `write` / `write_stream` / `save` / `extract` / `extract_all` | `fs.`                                                                 |
| **HTTP**        | `http.*`                                                                                                                                                                    | `http.`                                                               |
| **Sound**       | `sound.play`                                                                                                                                                                | `sound.`                                                              |

***

## 📏 Conventions

* **Colors** use 0–255 per channel.
* **Screen coordinates** are pixels, origin at the top-left corner.
* **Angles** are `vec3(pitch, yaw, roll)` in degrees, except `render.arc`, which takes radians.
* **Optional parameters** are marked `?`. Every other parameter is required. Calling a function with fewer arguments raises an error (for example `render.rect` needs `rounding`).
* **Indices:** combo selections, bone indices and hitbox ids are 0-based. Tables returned by the API are normal 1-based Lua arrays.
* **`end` is a Lua keyword.** Write `ui["end"]()` or use `ui.done()`, and `r["end"]` for the ray field.
* **Object lifetime:** entity, player, controller, weapon and panel objects wrap raw game pointers. Use them inside the callback that fetched them. Do not keep them across frames; keep an `index` or a [`handle`](/instances-and-types/types/handle.md) instead, and compare entities by `index`.
* **Numbers** are doubles. Integers above 2^53 lose precision.

***

## ⚠️ Known Limitations

* **`bit32` is 32-bit.** `user_cmd.buttons` and `defs.buttons` are 64-bit; use `cmd:has_button` / `cmd:add_button` / `cmd:remove_button` or arithmetic for those masks.
* **`config.set_*` writes the base value.** An active keybind override still wins in `config.get_*`.
* **No `os`, `io`, `debug` or `utf8`** without Insecure (`os` / `io`) — `debug` and `utf8` are never exposed.
* **Scripts are not auto-loaded**; load them from the Lua tab or with `scripts.load`.
* **Threading:** `render.*`, `ui.*`, `gui.*` are render-thread only; `panorama.*` is game-thread only.
* **`steam_id` and other 64-bit ids are doubles** and lose precision above 2^53. For an exact value read the handle / steam id through FFI.
* **Numbers above 2^53 are imprecise** generally, since Lua numbers are doubles.
