Menu
On this page
The core.ui module lets scripts build their own settings UI in the web menu. Every constructor takes a single options table and returns an element handle you can read, write, and mutate at runtime. Settings persist across sessions and are also reachable through core.get_setting() / core.set_setting().
local enabled = core.ui.checkbox{ id = "enabled", label = "Enable", panel = "General", default = false }
function on_tick()
if enabled:get() then
-- ...
end
end- IDs are namespaced per script as
lua.<script>.<id>(e.g.id = "enabled"inmy_script.luabecomeslua.my_script.enabled). An ID you pass that already starts withlua.is kept as-is. This keeps scripts from colliding with each other or with core settings. Prefer the handle (elem:get()) so you never have to type the full ID; if you usecore.get_setting, pass the full namespaced ID. - Custom tabs are allowed. Omit
tabto land in the shared Lua tab, or pass any name to create your own top-level tab. Reserved/core tab names (autos,combat,hacks,items,map,macros,keybinds,packets,debug,settings,player,admin) are rejected. Give your tabs an icon or fold several into a submenu withcore.ui.tab. - Idempotent. Re-running a constructor with an existing ID returns a handle to the existing element instead of creating a duplicate, so it's safe to build your UI at the top of the script.
Common options
Every constructor accepts these keys in its options table:
| Key | Type | Notes |
|---|---|---|
id | string | Required. Namespaced to lua.<script>.<id>. |
label | string | Display label. |
panel | string | Required. Card within the tab. |
tab | string | Top-level tab. Defaults to "Lua". |
section | string | In-page sub-tab this panel belongs to (see Organizing your UI). |
tooltip | string | Hover text. |
order | int | Sort order within the panel (lower first). |
description | string | Panel subtitle (card description). |
visible_when | string | ID of another element; this one is hidden unless that element is truthy. Namespaced like id. |
hidden | bool | Start hidden (toggle later with :set_visible). |
disabled | bool | Start non-interactive (toggle later with :set_enabled). |
dangerous | bool | Hidden unless "Show Dangerous" is on. |
transient | bool | Never saved to profiles (runtime-only). Text fields update as you type, before a row selection or action. |
critical | bool | string | Mark safety-critical; a string overrides the alert label. |
Creating widgets
core.ui.checkbox(opts) -> element
Available on GMS and CW
Boolean toggle. Extra options: default (bool), disclosure (bool, default false). A disclosure expands the following controls that use its ID as visible_when; other checkboxes remain switches.
local cb = core.ui.checkbox{ id = "enabled", label = "Enable", panel = "General", default = false }core.ui.slider_int(opts) -> element
Available on GMS and CW
Integer slider. Extra options: default, min, max, step (defaults: 0 / 0 / 100 / 1).
local spd = core.ui.slider_int{ id = "speed", label = "Speed", panel = "General",
default = 50, min = 0, max = 100, step = 5 }core.ui.slider_float(opts) -> element
Available on GMS and CW
Float slider. Extra options: default, min, max, step, rounding (defaults: 0 / 0 / 1 / 0.1 / 2 decimals).
local r = core.ui.slider_float{ id = "ratio", label = "Ratio", panel = "General",
default = 1.0, min = 0.0, max = 5.0, step = 0.1, rounding = 2 }core.ui.dropdown(opts) -> element
Available on GMS and CW
Single-select dropdown. Extra options: options (array of strings), default (0-based index), table (optional searchable table presentation). Its value is always the original selected option index, including after searching or filtering.
local target = core.ui.dropdown{ id = "target", label = "Target", panel = "Targeting",
options = { "Closest", "Random" }, default = 0 }For a searchable table, declare column labels and a separator used within each option. Panels, fields, and action buttons size and wrap automatically; scripts do not need spacing settings.
local targets = core.ui.dropdown{
id = "targets", label = "Targets", panel = "Targeting", transient = true,
options = { "One;West;Ready", "Two;East;Paused" },
table = {
separator = ";", labelColumn = 1,
columns = {
{ label = "Name" }, { label = "Region" },
{ label = "State", badges = { Ready = "success", Paused = "warning" } },
},
filters = { { label = "Available", column = 3, values = { "Ready" } } },
},
}table accepts:
| Key | Type | Notes |
|---|---|---|
separator | string | Required, non-empty delimiter. Every option must have one field per column; fields are trimmed. Avoid the delimiter within a field. |
columns | table[] | Required, non-empty. Each column has a label; optional badges maps cell text to primary, success, warning, danger, info, or muted. Unmapped badge values use muted. |
filters | table[] | Optional buttons, each with label, a 1-based column, and allowed values (strings). An All button is provided automatically. Search also applies to filtered rows. |
labelColumn | int | 1-based column used in the selection summary; default 1. |
image | table | Optional thumbnail on every row: kind ("minimap" or "item_icon") and a 1-based column whose cells hold the map or item ID. A row whose cell isn't a positive whole number shows a placeholder. |
selectionLabel | string | Selection marker and summary label; default Selected. |
searchPlaceholder | string | Search hint; default Search options…. |
description | string | Help text above the table. |
emptyOption | string | When this is the sole option, display an empty table without a selectable row. |
emptyText | string | Empty-table message; default No options available. |
Missing or inconsistent row data uses the ordinary dropdown. Updating options with :set_options() keeps the table description. Empty option lists show an empty table. Declaring table without column labels or a non-empty separator, or with an invalid image, raises an error.
core.ui.radio_group(opts) -> element
Available on GMS and CW
Like a dropdown but rendered as a row of buttons. Extra options: options, default (index). Value is the selected index.
local mode = core.ui.radio_group{ id = "mode", label = "Mode", panel = "General",
options = { "Safe", "Fast", "YOLO" }, default = 0 }core.ui.multi_select(opts) -> element
Available on GMS and CW
Listbox where several options can be checked. Extra options: options, default (array of indices). Value is a sorted array of selected indices.
local kinds = core.ui.multi_select{ id = "kinds", label = "Hit Kinds", panel = "Targeting",
options = { "Mobs", "Reactors", "NPCs" }, default = { 0 } }core.ui.input_text(opts) -> element
Available on GMS and CW
Text field. Extra options: default (string), flags (array of strings). Use flags = { "password" } to conceal the value and provide a Show/Hide button. Revealing does not change the setting; switching profile or session conceals it again and cancels pending edits.
local note = core.ui.input_text{ id = "note", label = "Note", panel = "General", default = "" }The password flag controls display only. The value is still saved unless transient = true.
core.ui.keybind(opts) -> element
Available on GMS and CW
Key capture. Extra option: default (key name string, e.g. "F8").
local key = core.ui.keybind{ id = "toggle_key", label = "Toggle Key", panel = "General", default = "F8" }core.ui.color(opts) -> element
Available on GMS and CW
RGBA color picker. Extra option: default (array of 4 floats { r, g, b, a }, each 0–1).
local col = core.ui.color{ id = "tint", label = "Tint", panel = "General", default = { 1, 0, 0, 1 } }core.ui.button(opts) -> element
Available on GMS and CW
One-shot action button — no value; poll it with :pressed() (see Buttons). Extra options: variant ("default", "destructive", "outline", "secondary", "ghost"), confirm (confirmation prompt shown before firing).
local run = core.ui.button{ id = "run", label = "Delete", panel = "Actions", variant = "destructive", confirm = "Are you sure?" }core.ui.static_text(opts) -> element
Available on GMS and CW
Display-only text (no value). Extra options: text (the content), style ("muted", "body", or "heading"), markdown (when true, render inline **bold**, *italic*, `code`). Update it later with :set_text().
local status = core.ui.static_text{ id = "status", panel = "Actions", style = "body", text = "idle" }core.ui.progress(opts) -> element
Available on GMS and CW
Read-only progress bar. Extra options: value, min, max (defaults 0 / 0 / 1), display ("percent" or "value"), color ("violet", "success", "warning", "danger", "info"). Drive it from script with :set_value().
local bar = core.ui.progress{ id = "bar", label = "Progress", panel = "Actions", value = 0, min = 0, max = 100, color = "success" }core.ui.image(opts) -> element
Available on GMS and CW
Display-only image from the game's own art (no value). Extra options: one source, either minimap (a map ID) or item_icon (an item ID); size ("small" 48 px square, "medium" 160 px tall, or "large" 288 px tall; medium and large span the panel; default "medium"); show_name (when true, show the map or item name under the image, or beside it at "small"). URLs and files are not accepted. The source is optional: without one the element shows a placeholder until you call :set_source(), and an ID the game has no image for shows "No image available" (at "small", a placeholder icon only). Passing both sources, an ID that isn't a positive whole number, or an unknown size raises an error.
local preview = core.ui.image{ id = "preview", label = "Current target", panel = "Map Preview",
minimap = 100000000, show_name = true }Pair it with a dropdown table's image column to pick from a list of maps. Build both from one list so the IDs can't drift apart; calling :set_source() with the image already shown does nothing, so it's safe every tick:
local MAPS = { { 100000000, "Henesys" }, { 101000000, "Ellinia" }, { 102000000, "Perion" } }
local options = {}
for i, m in ipairs(MAPS) do options[i] = m[1] .. "|" .. m[2] end
local map = core.ui.dropdown{ id = "map", label = "Map", panel = "Map Preview", options = options,
table = { separator = "|", labelColumn = 2, searchPlaceholder = "Search maps",
columns = { { label = "Map ID" }, { label = "Name" } },
image = { kind = "minimap", column = 1 } } }
function on_tick()
preview:set_source{ minimap = MAPS[map:get() + 1][1] }
endcore.ui.separator(opts) -> element
Available on GMS and CW
Visual divider. With a label it renders as a small section heading; without one, a plain rule.
core.ui.separator{ id = "sec1", label = "Advanced", panel = "General" }Organizing your UI
Bigger scripts quickly outgrow one flat page. Three tools keep the menu tidy: custom tab icons, nav groups (submenus), and in-page sections.
core.ui.tab(opts)
Available on GMS and CW
Sets navigation metadata for one of your tabs. Call it once at the top of the script — order relative to element creation doesn't matter, but the tab only shows up once at least one element is placed on it. Returns nothing.
| Key | Type | Notes |
|---|---|---|
name | string | Required. The tab this metadata applies to (the same string you pass as tab). |
icon | string | Sidebar icon — a kebab-case name from the icon set. |
group | string | Nav group label. Your tabs sharing the same group fold into one collapsible sidebar entry. |
group_icon | string | Icon for the group header. Among grouped tabs, the first one that sets it wins. |
order | int | Sort order among script tabs / within the group (lower first). |
Reserved tab names are rejected, plus the shared Lua tab (you can place elements there, but not restyle a surface every script shares). Groups only apply to script tabs — built-in tabs can't be regrouped or re-iconed.
local GROUP = "Azura's Scripts"
core.ui.tab{ name = "AIO Grinding", group = GROUP, group_icon = "crown", icon = "swords", order = 1 }
core.ui.tab{ name = "AIO Monster Park", group = GROUP, icon = "castle", order = 2 }
core.ui.tab{ name = "Secret Exploits", group = GROUP, icon = "skull", order = 3 }
core.ui.checkbox{ id = "enabled", label = "Enable", tab = "AIO Grinding", panel = "General", default = false }
-- ...The sidebar now shows a single collapsible Azura's Scripts entry with the three tabs inside it, instead of three flat nav items.
In-page sections (tab strips)
Pass section on elements to split a busy page into sub-tabs, like the built-in Player page's Equip / Use / Set-up / Etc / Cash inventory tabs. Panels that share a section render under one tab strip; panels without a section stay pinned above the strip — put your master toggle there.
local TAB = "AIO Grinding"
-- No section: always visible above the strip.
local enabled = core.ui.checkbox{ id = "enabled", label = "Enable", tab = TAB, panel = "General", default = false }
-- Buffs | Skills | Misc
core.ui.checkbox{ id = "haste", label = "Haste", tab = TAB, section = "Buffs", panel = "Party Buffs", default = true }
core.ui.checkbox{ id = "hs", label = "Holy Symbol", tab = TAB, section = "Buffs", panel = "Party Buffs", default = true }
core.ui.slider_int{ id = "combo", label = "Combo Count", tab = TAB, section = "Skills", panel = "Attacking",
default = 3, min = 1, max = 10 }
core.ui.checkbox{ id = "loot", label = "Auto Loot", tab = TAB, section = "Misc", panel = "Other", default = true }Sections appear in the order they are first used. Keep every element of one panel in the same section, and move elements later with elem:set_section(). Sections are purely visual — IDs, values, and saved profiles are unaffected.
Icon set
icon and group_icon accept these kebab-case names (a curated subset of lucide.dev). Unknown names quietly fall back to the default icon, so typos never break a menu.
activity, alarm-clock, anchor, archive, award, axe, backpack, banknote, bell, bird, bomb, book, book-open, bot, braces, brain, brush, bug, calendar, camera, car, castle, circle-dollar-sign, clock, cloud-lightning, code-2, cog, coins, compass, cpu, crosshair, crown, database, diamond, dices, dna, droplet, egg, eye, eye-off, feather, file-code, filter, fish, flag, flame, flask-conical, footprints, gamepad-2, gauge, gem, ghost, gift, glasses, hammer, hash, heart, hourglass, house, infinity, key, landmark, layers, layout-grid, leaf, link, list, lock, magnet, mail, map, map-pin, medal, message-square, moon, mountain, music, navigation, package, palette, pause, paw-print, pen, percent, pickaxe, pin, plane, play, puzzle, rabbit, refresh-cw, repeat, rocket, route, ruler, scale, scissors, scroll, scroll-text, search, send, settings, shield, shield-check, ship, shopping-cart, skull, sliders-horizontal, snowflake, sparkles, sprout, star, store, sun, sword, swords, table, target, tent, terminal, test-tube, timer, trending-up, trees, trophy, truck, umbrella, unlock, user, users, wallet, wand-2, watch, waves, wheat, wrench, zap
The element handle
Every constructor returns a handle. A handle stays safe even if the element is later removed (calls become no-ops). Mutator methods return the handle, so they can be chained.
element:get() -> any
Available on GMS and CW
Returns the current value (bool / number / string / array, depending on widget type).
if cb:get() then core.log("on") endelement:set(value) -> boolean
Available on GMS and CW
Sets the value, coerced to the element's type, and pushes it to the web menu.
spd:set(75)element:id() -> string
Available on GMS and CW
Returns the full namespaced ID (e.g. lua.my_script.enabled).
element:set_label(label) -> element
Available on GMS and CW
Changes the display label at runtime.
element:set_tooltip(text) -> element
Available on GMS and CW
Changes the tooltip at runtime.
element:set_visible(visible) -> element
Available on GMS and CW
Shows or hides the element. Hidden elements are removed from the layout.
spd:set_visible(mode:get_index() ~= 2)element:visible() -> boolean
Available on GMS and CW
Returns whether the element is currently visible.
element:set_enabled(enabled) -> element
Available on GMS and CW
Enables or disables interaction (the element stays visible but greys out).
element:enabled() -> boolean
Available on GMS and CW
Returns whether the element is currently enabled.
element:set_visible_when(other_id) -> element
Available on GMS and CW
Binds conditional visibility to another element's value at runtime.
element:set_order(order) -> element
Available on GMS and CW
Changes the sort order within the panel.
element:set_panel(panel) -> element
Available on GMS and CW
Moves the element to a different panel.
element:set_section(section) -> element
Available on GMS and CW
Moves the element to a different in-page section (see Organizing your UI). Pass "" to detach it from the strip and pin it above.
element:set_options(options) -> element
Available on GMS and CW
Replaces the option list of a dropdown, radio_group, or multi_select at runtime. The selected index is clamped to the new range.
target:set_options({ "Closest", "Random", "Boss", "Elite" })element:get_index() -> number
Available on GMS and CW
Returns the selected index of a dropdown or radio_group.
element:set_index(index) -> element
Available on GMS and CW
Sets the selected index of a dropdown or radio_group.
element:get_selected() -> any
Available on GMS and CW
For dropdown/radio_group returns the selected option string; for multi_select returns the array of selected indices.
element:set_selected(indices) -> element
Available on GMS and CW
Sets the selected indices of a multi_select (array of indices).
element:set_min(value) -> element
Available on GMS and CW
Changes a slider's minimum at runtime (value is re-clamped).
element:set_max(value) -> element
Available on GMS and CW
Changes a slider's maximum at runtime (value is re-clamped).
element:set_step(value) -> element
Available on GMS and CW
Changes a slider's step at runtime.
element:set_text(text) -> element
Available on GMS and CW
Changes the text of a static_text element.
element:set_value(value) -> element
Available on GMS and CW
Sets the current value of a progress bar.
element:set_source(source) -> element
Available on GMS and CW
Changes the picture of an image element: { minimap = id } or { item_icon = id }. Pass nil to clear it; a table with neither key raises an error.
element:source() -> table | nil
Available on GMS and CW
Returns the current source of an image element as { minimap = id } or { item_icon = id }, or nil when none is set.
element:pressed() -> boolean
Available on GMS and CW
For a button: returns true exactly once after each click, then clears. Poll it in on_tick.
element:remove()
Available on GMS and CW
Removes the element from the menu.
Buttons
There is no UI event callback system; buttons are polled. :pressed() returns true once per click and then resets, so check it inside on_tick:
local run = core.ui.button{ id = "run", label = "Run Once", panel = "Actions" }
function on_tick()
if run:pressed() then
core.log("button clicked")
end
endWidget reference
| Constructor | Value (:get()) | Extra options | Key methods |
|---|---|---|---|
checkbox | bool | default | :get :set |
slider_int | number | default min max step | :get :set :set_min :set_max :set_step |
slider_float | number | default min max step rounding | same as slider_int |
dropdown | index | options default | :get_index :set_index :get_selected :set_options |
radio_group | index | options default | same as dropdown |
multi_select | index array | options default | :get_selected :set_selected :set_options |
input_text | string | default flags | :get :set |
keybind | string | default | :get :set |
color | {r,g,b,a} | default | :get :set |
button | — | variant confirm | :pressed |
static_text | — | text style markdown | :set_text |
progress | number | value min max display color | :set_value |
image | — | minimap item_icon size show_name | :set_source :source |
separator | — | — | — |
Full example
plugin = {
name = "UI Demo",
version = "1.0.0",
author = "Author",
description = "Showcases the core.ui API",
load = true
}
local TAB = "UI Demo"
local enabled = core.ui.checkbox{ id = "enabled", label = "Enable", tab = TAB, panel = "General", default = true }
local mode = core.ui.radio_group{ id = "mode", label = "Mode", tab = TAB, panel = "General",
options = { "Safe", "Fast", "YOLO" }, default = 0 }
local speed = core.ui.slider_int{ id = "speed", label = "Speed", tab = TAB, panel = "General",
default = 50, min = 0, max = 100, step = 5 }
local target = core.ui.dropdown{ id = "target", label = "Target", tab = TAB, panel = "Targeting",
options = { "Closest", "Random" }, default = 0 }
local status = core.ui.static_text{ id = "status", tab = TAB, panel = "Actions", style = "body", text = "idle" }
local bar = core.ui.progress{ id = "bar", label = "Progress", tab = TAB, panel = "Actions", value = 0, min = 0, max = 100 }
local run = core.ui.button{ id = "run", label = "Run Once", tab = TAB, panel = "Actions" }
local running, ticks = false, 0
function on_tick()
if run:pressed() and enabled:get() then
running, ticks = true, 0
status:set_text("running (mode=" .. mode:get_selected() .. ", target=" .. target:get_selected() .. ")")
target:set_options({ "Closest", "Random", "Boss", "Elite" }) -- grow the list at runtime
end
if running then
ticks = ticks + 1
bar:set_value(math.min(ticks, 100))
if ticks >= 100 then
running = false
status:set_text("done")
end
end
speed:set_visible(mode:get_index() ~= 2) -- hide Speed in YOLO mode
endLegacy: core.menu
The original core.menu functions still work — they are thin wrappers over core.ui that force the Lua tab and the legacy lua.<id> naming (no per-script segment). They return a boolean instead of a handle, so to read/write their values you use core.get_setting / core.set_setting with the full lua.<id> path. Prefer core.ui for new scripts.
core.menu.create_checkbox(id, label, tab, panel, default_value [, tooltip]) -> boolean
Available on GMS and CW
Creates a checkbox setting (the tab argument is ignored — always "Lua").
core.menu.create_checkbox("myScript.enabled", "Enable Feature", "Lua", "My Script", false)
-- Registered as id="lua.myScript.enabled"; read with core.get_setting("lua.myScript.enabled")core.menu.create_slider_int(id, label, tab, panel, default, min, max [, step [, tooltip]]) -> boolean
Available on GMS and CW
Creates an integer slider setting. step defaults to 1.
core.menu.create_slider_float(id, label, tab, panel, default, min, max [, step [, tooltip [, rounding]]]) -> boolean
Available on GMS and CW
Creates a float slider setting. step defaults to 0.1, rounding defaults to 2.
core.menu.create_dropdown(id, label, tab, panel, options, default_index [, tooltip]) -> boolean
Available on GMS and CW
Creates a dropdown setting. options is a Lua array of strings; default_index is 0-based.
core.menu.create_keybind(id, label, tab, panel [, default_key [, tooltip]]) -> boolean
Available on GMS and CW
Creates a keybind setting.
core.menu.create_input_text(id, label, tab, panel [, default_value [, tooltip]]) -> boolean
Available on GMS and CW
Creates a text input setting.