Script UI Configuration API

The Configuration Schema system allows scripts to define custom UI forms for user input. This provides a rich set of components for creating complex configuration interfaces.

Schema Structure

config_schema = {
    version = "1.0",
    layout = {
        -- Root layout component
    }
}

Special Configuration Fields

When using a tab-based layout, the system automatically adds special fields to track UI state:

  • active_tab (string): The label of the currently selected tab (e.g., "General", "Advanced")
  • active_tab_index (number): The zero-based index of the currently selected tab (0, 1, 2, etc.)

These fields are automatically set when the Start button is pressed and can be accessed in your script's on_start function.

function on_start(config)
    -- Access which tab was selected
    logger:info("Active tab: " .. tostring(config.active_tab))
    logger:info("Tab index: " .. tostring(config.active_tab_index))

    -- Use it to adjust behavior
    if config.active_tab == "Quick Start" then
        -- User started from Quick Start tab
        use_preset_defaults()
    elseif config.active_tab == "Advanced" then
        -- User started from Advanced tab
        use_custom_settings(config)
    end
end

Note: These fields are only available when using the tabs layout type at the root level. Non-tabbed layouts will not have these fields.

Layout Components

Section

Groups related components together with optional header.

{
    type = "section",
    label = "General Settings",
    description = "Configure basic script options",
    children = {
        -- Child components
    }
}

Tabs

Creates a tabbed interface for organizing settings.

{
    type = "tabs",
    children = {
        {
            type = "section",
            label = "Tab 1",
            children = { -- Tab content }
        },
        {
            type = "section",
            label = "Tab 2",
            children = { -- Tab content }
        }
    }
}

Grid

Arranges components in a grid layout.

{
    type = "grid",
    props = {
        columns = 2  -- Number of columns
    },
    children = {
        -- Components arranged in grid
    }
}

VStack / HStack

Vertical or horizontal stack layouts.

{
    type = "vstack",  -- or "hstack"
    children = {
        -- Stacked components
    }
}

Input Components

Text Input

{
    type = "text",
    id = "username",
    label = "Username",
    description = "Enter your character name",
    props = {
        placeholder = "Enter text...",
        default = "DefaultValue"
    }
}

Button

{
    type = "button",
    id = "btn_id",
    label = "Click Me",
    props = {
        variant = "primary",
        size = "md",
        data_component_id = "table_id"  -- Optional: clears associated table's profile data on click
    }
}

function script:on_button_clicked(btn_id, values)
    -- Handle button click event
    return config:get_schema(values)
end

Button Properties:

PropertyTypeRequiredDescription
variantstringNoButton style: "primary", "secondary", etc. Default: "primary"
sizestringNoButton size: "sm", "md", or "lg". Default: "md"
data_component_idstringNoWhen provided, clicking this button will clear the profile configuration data associated with the specified table component ID. This is useful for "Clear" or "Reset" buttons that should remove saved table data (like saved items in a dynamic table). The table component with the matching id will have its profile data removed when this button is clicked

Button Click Handler:

Buttons trigger the on_button_clicked callback in your script. When a button has data_component_id set, the associated table's profile data is automatically cleared before the callback is invoked.

Number Input

{
    type = "number",
    id = "quantity",
    label = "Quantity",
    description = "How many items to process",
    props = {
        min = 1,
        max = 1000,
        step = 1,
        default = 100
    }
}

Checkbox

{
    type = "checkbox",
    id = "use_bank",
    label = "Use Bank",
    description = "Enable banking between trips",
    props = {
        default = true
    }
}

Select (Dropdown)

{
    type = "select",
    id = "location",
    label = "Location",
    description = "Choose mining location",
    props = {
        options = {
            {value = "varrock", label = "Varrock Mine"},
            {value = "lumbridge", label = "Lumbridge Mine"},
            {value = "falador", label = "Falador Mine"}
        },
        default = "varrock",
        placeholder = "Select a location..."
    }
}

Slider

{
    type = "slider",
    id = "speed",
    label = "Action Speed",
    description = "Delay between actions in milliseconds",
    props = {
        min = 100,
        max = 5000,
        step = 50,
        default = 1000
    }
}

Multi-Select

{
    type = "multi_select",
    id = "items",
    label = "Items to Collect",
    description = "Select multiple items",
    props = {
        options = {
            {value = "iron", label = "Iron ore"},
            {value = "coal", label = "Coal"},
            {value = "gold", label = "Gold ore"}
        },
        default = {"iron", "coal"}
    }
}

Data Components

Table

{
    type = "table",
    id = "item_prices",
    label = "Price Configuration",
    props = {
        keys = {"item"},  -- Use "item" column to generate unique row keys
        columns = {
            {key = "item", label = "Item", type = "string"},
            {key = "price", label = "Price", type = "number"},
            {key = "enabled", label = "Enabled", type = "boolean"}
        },
        data = {
            {item = "Iron ore", price = 100, enabled = true},
            {item = "Coal", price = 150, enabled = false}
        },
        selected = {
            {item = "Iron ore", price = 100, enabled = true},
            {item = "Coal", price = 150, enabled = false}
        },  -- Selected rows as full objects
        selection_mode = "multiple",  -- "single", "multiple", or "none"
        sortable = true
    }
}

Table Properties:

PropertyTypeRequiredDescription
columnsarrayYesColumn definitions with key, label, optional type, optional width, and optional component (see below)
dataarrayYesArray of row objects. Each row should have properties matching the column key values
keysarrayNoArray of column key values used to generate a unique identifier for each row. If not provided, the system will attempt to use an id field from row data. The row key is generated by joining the values of these specified columns with underscores (e.g., if keys = {"name", "level"}, a row with name = "Iron" and level = 15 will have key "Iron_15")
selectedarray or objectNoPre-selected rows. For multiple selection, use an array of row objects; for single selection, use a single row object. The system matches rows by comparing the generated keys from the keys property
selection_modestringNoSelection mode: "single", "multiple", or "none". Default: "single"
sortablebooleanNoEnable column sorting. Default: false

Column Properties:

PropertyTypeRequiredDescription
keystringYesThe property name in the row data to display in this column
labelstringNoDisplay label for the column header
typestringNoData type hint: "string", "number", or "boolean"
widthstringNoColumn width (e.g., "200px", "150px")
componentobjectNoCustom component to render in cells of this column (see Column Component Rendering below)

Column Component Rendering:

Columns can render custom components instead of simple text values. When a column has a component property, that component will be rendered in each cell of that column. The component has access to all row data through the values object.

Key Behaviors:

  1. Selection Requirement: Components in table cells are automatically disabled if the row is not selected. Only selected rows allow editing of cell components. This ensures that users can only modify data for rows they've explicitly chosen.

  2. Row Data Access: Components in table cells receive the full row data merged with any updates made to the selected row. For example, if a row has {name = "Iron ore", price = 100, quantity = 10}, the component can access all these values through the values object.

  3. Component IDs: If a column component has an id, it will be used as-is. If a column component does not have an id, the system automatically uses the column's key as the component ID. This ensures each component has a unique identifier for value tracking.

  4. Value Updates: When a component in a cell changes its value, the system automatically updates the selected row object. The updated row is then stored in the table's selection, preserving all changes made through cell components.

  5. Fallback Behavior: If a column doesn't have a component property, it will fall back to simple text rendering using row[col.key].

{
    type = "table",
    id = "item_config",
    label = "Item Configuration",
    props = {
        keys = {"name"},  -- Use "name" column to generate row keys
        columns = {
            {key = "name", label = "Item Name"},  -- Simple text rendering
            {
                key = "quantity",
                label = "Quantity",
                component = {
                    type = "number",
                    -- No id specified, so "quantity" will be used as the component id
                    props = {
                        min = 1,
                        max = 1000,
                        default = 1
                    }
                }
            },
            {
                key = "enabled",
                label = "Enabled",
                component = {
                    type = "checkbox",
                    props = {
                        default = true
                    }
                }
            },
            {
                key = "priority",
                label = "Priority",
                component = {
                    id = "item_priority",  -- Explicit ID
                    type = "select",
                    props = {
                        options = {
                            {value = "low", label = "Low"},
                            {value = "medium", label = "Medium"},
                            {value = "high", label = "High"}
                        },
                        default = "medium"
                    }
                }
            }
        },
        data = {
            {name = "Iron ore", quantity = 10, enabled = true, priority = "high"},
            {name = "Coal", quantity = 5, enabled = false, priority = "low"}
        },
        selection_mode = "multiple"
    }
}

Important Notes for Column Components:

  1. Selection-Based Editing: Users must select a row (via checkbox/radio or row click) before they can edit components in that row's cells. Unselected rows have disabled components. This prevents accidental edits and ensures users explicitly choose which rows to configure.

  2. Value Persistence: Changes made to cell components are stored directly in the selected row object. When the configuration is submitted, the selected array contains the updated row objects with all cell component changes applied. The original data array remains unchanged.

  3. Row Key Generation: The keys property in table props specifies which column values are used to create a unique identifier for each row. For example, if keys = {"name", "level"}, a row with name = "Iron ore" and level = 15 will have the key "Iron ore_15". This key is used internally to match rows during selection and updates.

  4. Selection Format: The selected property should contain full row objects (not just IDs or keys). The system matches rows by comparing the generated keys. For example:

-- Multiple selection
selected = {
    {name = "Iron ore", quantity = 10, enabled = true},
    {name = "Coal", quantity = 5, enabled = false}
}

-- Single selection
selected = {name = "Iron ore", quantity = 10, enabled = true}
  1. Component ID Auto-Assignment: If a column component doesn't specify an id, the system automatically uses the column's key as the component ID. This ensures each component has a unique identifier while keeping the schema simple.

Example: Table with Mixed Column Types

{
    type = "table",
    id = "trading_config",
    label = "Trading Configuration",
    props = {
        columns = {
            {key = "item", label = "Item"},  -- Simple text
            {
                key = "min_price",
                label = "Min Price",
                component = {
                    type = "number",
                    props = {
                        min = 0,
                        max = 1000000,
                        default = 0
                    }
                }
            },
            {
                key = "max_price",
                label = "Max Price",
                component = {
                    type = "number",
                    props = {
                        min = 0,
                        max = 1000000,
                        default = 1000000
                    }
                }
            },
            {
                key = "action",
                label = "Action",
                component = {
                    type = "select",
                    props = {
                        options = {
                            {value = "buy", label = "Buy"},
                            {value = "sell", label = "Sell"},
                            {value = "skip", label = "Skip"}
                        },
                        default = "buy"
                    }
                }
            }
        },
        keys = {"item"},  -- Use "item" column to generate row keys
        data = {
            {item = "Iron ore", min_price = 50, max_price = 150, action = "buy"},
            {item = "Coal", min_price = 100, max_price = 200, action = "sell"}
        },
        selection_mode = "multiple"
    }
}

List

{
    type = "list",
    id = "tasks",
    label = "Task Queue",
    props = {
        items = {
            {id = 1, label = "Mine iron"},
            {id = 2, label = "Bank ores"},
            {id = 3, label = "Repeat"}
        }
    }
}

Display Components

Text Display (Read-only)

{
    type = "text_display",
    id = "info",
    label = "Information",
    props = {
        text = "This is read-only informational text."
    }
}

Image

{
    type = "image",
    id = "preview",
    label = "Location Preview",
    props = {
        url = "https://example.com/image.png",
        width = 200,
        height = 150
    }
}

Common Component Properties

All components support these optional properties:

{
    id = "unique_identifier",      -- Required for input components
    label = "Display Label",        -- Component label
    description = "Helper text",    -- Description/tooltip
    visible = true,                 -- Boolean or conditional (see below)
    enabled = true,                 -- Boolean or conditional
    props = {                       -- Component-specific properties
        -- Component-specific options
    }
}

Conditional Visibility

Components can be shown/hidden based on other field values:

{
    type = "number",
    id = "max_price",
    label = "Maximum Price",
    visible = {
        field = "use_price_limit",
        operator = "equals",
        value = true
    }
}

Operators:

OperatorDescription
"equals"Field equals value
"not_equals"Field does not equal value
"contains"Field contains value (for strings/arrays)
"greater_than"Field is greater than value
"less_than"Field is less than value
"in"Field value is in array

Validation

Add validation rules to ensure valid configuration:

{
    type = "number",
    id = "quantity",
    label = "Quantity",
    validation = {
        rule = {
            type = "range",
            min = 1,
            max = 1000
        },
        message = "Quantity must be between 1 and 1000"
    }
}

Validation Types:

TypeDescription
"required"Field must have a value
"range"Number must be within min/max
"custom"Custom Lua validator function

Dynamic Configuration Callbacks

Scripts can implement callbacks to update configuration dynamically:

-- Called when configuration values change
function on_config_changed(values)
    -- values is a table of current configuration
    -- Return updated schema based on current values
    return config_schema
end

Examples

Complete Configuration Schema Example

config_schema = {
    version = "1.0",
    layout = {
        type = "tabs",
        children = {
            -- General Settings Tab
            {
                type = "section",
                label = "General",
                children = {
                    {
                        type = "vstack",
                        children = {
                            {
                                type = "select",
                                id = "location",
                                label = "Mining Location",
                                description = "Choose where to mine",
                                props = {
                                    options = {
                                        {value = "varrock_east", label = "Varrock East Mine"},
                                        {value = "varrock_west", label = "Varrock West Mine"},
                                        {value = "mining_guild", label = "Mining Guild"}
                                    },
                                    default = "varrock_east"
                                }
                            },
                            {
                                type = "multi_select",
                                id = "ores",
                                label = "Ores to Mine",
                                description = "Select which ores to mine",
                                props = {
                                    options = {
                                        {value = "iron", label = "Iron"},
                                        {value = "coal", label = "Coal"},
                                        {value = "mithril", label = "Mithril"}
                                    },
                                    default = {"iron"}
                                }
                            },
                            {
                                type = "checkbox",
                                id = "use_bank",
                                label = "Use Banking",
                                description = "Bank ores when inventory is full",
                                props = {default = true}
                            }
                        }
                    }
                }
            },

            -- Advanced Settings Tab
            {
                type = "section",
                label = "Advanced",
                children = {
                    {
                        type = "grid",
                        props = {columns = 2},
                        children = {
                            {
                                type = "number",
                                id = "min_delay",
                                label = "Minimum Delay (ms)",
                                props = {min = 50, max = 5000, default = 600}
                            },
                            {
                                type = "number",
                                id = "max_delay",
                                label = "Maximum Delay (ms)",
                                props = {min = 50, max = 5000, default = 1200}
                            }
                        }
                    },
                    {
                        type = "slider",
                        id = "antiban_level",
                        label = "Anti-ban Level",
                        description = "Higher values add more randomization",
                        props = {min = 1, max = 10, step = 1, default = 5}
                    },
                    {
                        type = "checkbox",
                        id = "enable_breaks",
                        label = "Enable Breaks",
                        props = {default = false}
                    },
                    {
                        type = "number",
                        id = "break_interval",
                        label = "Break Interval (minutes)",
                        visible = {
                            field = "enable_breaks",
                            operator = "equals",
                            value = true
                        },
                        props = {min = 5, max = 120, default = 30}
                    }
                }
            }
        }
    }
}

-- Handle configuration changes
function on_config_changed(values)
    logger:info("Configuration updated: " .. tostring(values.location))

    -- Update schema dynamically if needed
    return config_schema
end

Table with Clear Button Example

This example demonstrates using a button with data_component_id to clear saved table data:

config_schema = {
    version = "1.0",
    layout = {
        type = "vstack",
        children = {
            {
                type = "section",
                label = "Item Selection",
                children = {
                    {
                        type = "table",
                        id = "selected_items",
                        label = "Items to Process",
                        description = "Select items from the available list. All items are selected by default.",
                        props = {
                            keys = {"name"},  -- Use "name" column to generate row keys
                            columns = {
                                {key = "name", label = "Item Name", width = "200px"},
                                {key = "level", label = "Required Level", width = "150px"},
                                {key = "xp", label = "XP", width = "100px"}
                            },
                            data = {
                                {name = "Iron ore", level = 15, xp = 35},
                                {name = "Coal", level = 30, xp = 50},
                                {name = "Mithril ore", level = 55, xp = 80},
                                {name = "Adamantite ore", level = 70, xp = 95},
                                {name = "Runite ore", level = 85, xp = 125}
                            },
                            selection_mode = "multiple"
                        }
                    },
                    {
                        type = "hstack",
                        children = {
                            {
                                type = "button",
                                id = "refresh_items",
                                label = "Refresh Items",
                                props = {
                                    variant = "primary"
                                }
                            },
                            {
                                type = "button",
                                id = "clear_saved",
                                label = "Clear Saved Items",
                                props = {
                                    variant = "secondary",
                                    data_component_id = "selected_items"  -- Clears profile data for this table
                                }
                            }
                        }
                    }
                }
            }
        }
    }
}

-- Handle button clicks
function script:on_button_clicked(btn_id, values)
    if btn_id == "refresh_items" then
        -- Refresh the table data dynamically
        return update_table_with_new_items()
    elseif btn_id == "clear_saved" then
        -- The data_component_id prop already cleared the profile data
        -- Just update the schema to reflect the cleared state
        return config_schema
    end
    return config_schema
end

Key Features Demonstrated:

  1. data_component_id on Button: The "Clear Saved Items" button has data_component_id = "selected_items", which means clicking it will:
    • Remove any saved profile configuration data for the selected_items table
    • Allow the table to reset to its default state
    • Useful when users want to clear custom items they've added to the table

Best Practices

  1. Configuration Validation: Always validate user input and provide clear error messages through validation rules.
  2. Conditional UI: Use conditional visibility to simplify complex configurations by showing/hiding relevant options.
  3. Descriptive Labels: Provide clear labels and descriptions for all configuration options.
  4. Layout Organization: Use tabs and sections to organize complex configurations into logical groups.
  5. Tab Selection Tracking: Use the active_tab field to detect which tab users started from and adjust default behaviors accordingly.

Related APIs