---
title: "Modules"
description: "Create a module, react to its lifecycle, and read or control every other module."
---

> Documentation Index
> Fetch the complete documentation index at: https://aesthetic-docs.pages.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# Modules

## Your module

```lua
local module = ui.create("draw-square", "My ESP", "optional description")
```

| Method | Does |
|--------|------|
| `ui.create(icon, name[, description])` | Registers a module in the Scripts tab and returns it |
| `module:event(name, fn)` | Subscribes to an [event](/events), `"enable"` or `"disable"` |
| `module:enabled()` | `true` while enabled |
| `module:set_enabled(bool)` | Enables or disables it |
| `module:toggle()` | Flips it |

Settings go on the same object (`module:slider(...)`, `module:switch(...)`, …) and are
covered in [Settings](/settings). Those declarators also exist on nested menus from
`item:create()`; the four methods above exist only on the module itself.

One callback per event: a second `module:event("tick", ...)` replaces the first.

```lua
module:event("enable", function()
aesthetic.log("on")
end)
```

Callbacks run only while the module is enabled, and an error inside one disables it.

## Persistence

Setting values and enabled state survive restarts and reloads. They are keyed by module name
and item path (`"Parent.Child"` for nested items), so **renaming drops the stored value**.
`item:name(...)` changes the GUI label only, not the path.

## Other modules

The `modules` global reads and drives every registered module, built-ins included.

```lua
modules.list()      -- all module names
modules.active()    -- enabled names
modules.binds()     -- { { module = "Flight", keys = { "R" }, type = "toggle" }, ... }

local flight = modules.get("Flight")    -- case-insensitive, nil if missing
flight:enabled()
flight:set_enabled(true)
flight:toggle()
flight:binds()                          -- { { keys = { "R" }, type = "toggle" }, ... }
```

`flight.name`, `.title`, `.description`, `.icon` and `.category` describe it (`.icon` is the
GUI glyph, `""` when none). A bind `type` is `"toggle"` or `"hold"`.

### Their settings

`ref:settings()` lists setting names in GUI order. `ref:setting(name)` is case-insensitive and
returns a handle with `:get()`, `:set(v)`, `:reset()` and a `type` field:

| `type` | Value |
|--------|-------|
| `boolean` | boolean |
| `number` | number, clamped to the setting's range |
| `string` | string |
| `key` | array of key names, plus `:pressed()` |
| `color` | `0xAARRGGBB` |
| `selection` | option name |
| `multi_selection` | array of option names |
| `order` | permutation of the current key array |

```lua
local speed = modules.get("Speed")
for _, name in ipairs(speed:settings()) do
aesthetic.log(name .. " = " .. tostring(speed:setting(name):get()))
end
speed:setting("Mode"):set("Strafe")     -- unknown options and keys raise an error
```

Writes take the same path as the GUI: the module reacts immediately and the value persists.

## ui.find

`ui.find` resolves any module's setting into a full [menu item](/settings#menuitem), which
`modules.get(...):setting(...)` does not give you.

```lua
local item   = ui.find("Auto Totem", "Min Health")
local item   = ui.find("Combat", "Auto Totem", "Min Health")  -- category first
local nested = ui.find("My ESP", "ESP.Thickness")             -- nested path or bare name
```

Matching is case-insensitive; no match returns `nil`.

| Source | What the item can do |
|--------|----------------------|
| Your own module | Everything |
| Built-in module | `get`, `set`, `reset`, `type`, `set_callback`, `pressed`; `list` on combo and selectable. Layout methods (`name`, `visibility`, `create`, `color_picker`, `update`) raise an error |
| Another script's module | Same as your own, but the wrapper goes stale when that script reloads. Call `find` again |

Source: https://aesthetic-docs.pages.dev/modules/index.md
