Docs Reference

Lua API reference

This page is generated from the server's function registry. Each function lists the contexts it is available in: ui_event (form events), report (report expressions) and job (scheduled and on-demand jobs).

artifacts

Files a job run produces, downloadable from the run's history.

Available in: job.

artifacts.save

artifacts.save(name, content, {mime}?) → {name, size}

Saves a file for this run, within the workspace's artifact quota.

Parameter Type Description
name string File name: letters, digits, spaces, '.', '_' or '-' (at most 128 characters). Saving the same name again replaces it.
content string File content (at most 16 MiB).
options (optional) table mime: the media type (default from the file extension).

Errors:

artifacts.save("summary.csv", csv.encode(rows, {columns = {"id", "total"}}))

crypto

Pure cryptographic hashing, HMAC signatures, binary encoding and secure random generators. Evaluated in-memory with zero network or DNS lookups.

Available in: ui_event, report, job.

crypto.sha256

crypto.sha256(data) → string

Calculates the SHA-256 hash of a string.

Parameter Type Description
data string Data to hash.

Returns: hex-encoded SHA-256 digest (64 characters)

local hash = crypto.sha256("hello world")

crypto.sha512

crypto.sha512(data) → string

Calculates the SHA-512 hash of a string.

Parameter Type Description
data string Data to hash.

Returns: hex-encoded SHA-512 digest (128 characters)

local hash = crypto.sha512("hello world")

crypto.hmac_sha256

crypto.hmac_sha256(key, message) → string

Calculates an HMAC-SHA256 signature.

Parameter Type Description
key string Secret key.
message string Message payload.

Returns: hex-encoded HMAC-SHA256 signature

local sig = crypto.hmac_sha256("secret-key", "payload")

crypto.hmac_sha512

crypto.hmac_sha512(key, message) → string

Calculates an HMAC-SHA512 signature.

Parameter Type Description
key string Secret key.
message string Message payload.

Returns: hex-encoded HMAC-SHA512 signature

local sig = crypto.hmac_sha512("secret-key", "payload")

crypto.base64_encode

crypto.base64_encode(data) → string

Encodes a string using standard Base64 with padding.

Parameter Type Description
data string Data to encode.

Returns: standard Base64-encoded string (RFC 4648 §4)

local b64 = crypto.base64_encode("credentials:secret")

crypto.base64_decode

crypto.base64_decode(data) → string|nil, err

Decodes a standard Base64 string.

Parameter Type Description
data string Base64-encoded string.

Returns: decoded string (or nil, error)

local plain, err = crypto.base64_decode("Y3JlZGVudGlhbHM6c2VjcmV0")

crypto.base64url_encode

crypto.base64url_encode(data) → string

Encodes a string using unpadded URL-safe Base64.

Parameter Type Description
data string Data to encode.

Returns: unpadded URL-safe Base64-encoded string (RFC 4648 §5)

local enc = crypto.base64url_encode('{"alg":"none"}')

crypto.base64url_decode

crypto.base64url_decode(data) → string|nil, err

Decodes an unpadded or padded URL-safe Base64 string.

Parameter Type Description
data string URL-safe Base64-encoded string.

Returns: decoded string (or nil, error)

local plain, err = crypto.base64url_decode("eyJhbGciOiJub25lIn0")

crypto.hex_encode

crypto.hex_encode(data) → string

Encodes bytes to a hexadecimal string.

Parameter Type Description
data string Raw bytes or string.

Returns: lowercase hexadecimal string

local h = crypto.hex_encode("abc")

crypto.hex_decode

crypto.hex_decode(data) → string|nil, err

Decodes a hexadecimal string to raw bytes.

Parameter Type Description
data string Hexadecimal string.

Returns: decoded string (or nil, error)

local raw, err = crypto.hex_decode("616263")

crypto.random_hex

crypto.random_hex(n) → string

Generates cryptographically secure random bytes as a hex string.

Parameter Type Description
n number Number of random bytes (at least 1, at most 1024).

Returns: hexadecimal string of length 2n

local token = crypto.random_hex(16)

crypto.uuid

crypto.uuid() → string

Generates a cryptographically random RFC 4122 v4 UUID.

Returns: canonical lowercase RFC 4122 v4 UUID string

local id = crypto.uuid()

csv

CSV parsing and writing (RFC 4180 quoting). All cell values are strings when parsing; a leading UTF-8 byte-order mark is ignored. Rows may have differing lengths. Unknown option names raise an error.

Available in: ui_event, report, job.

csv.parse

csv.parse(s, opts?)

Parses all of s into an array of rows. On malformed CSV returns nil and a message of the form "line N: message".

Parameter Type Description
s string CSV text.
opts (optional) table {header = true, delimiter = ",", trim = false}. With header = true each row is a table keyed by the header names (empty names become column_N, duplicates get _2, _3 suffixes; short rows are padded with empty strings, extra cells are named column_N). With header = false rows are arrays of strings. trim = true strips surrounding whitespace from every cell. delimiter must be a single character.

Returns: table|nil, string|nil

Errors:

  • invalid option
local rows, err = csv.parse("name,qty\nbolt,3\n")
return rows[1].name  -- "bolt"

csv.rows

csv.rows(s, opts?)

Returns an iterator yielding one row per call, for use in a generic for loop. Malformed CSV raises an error when the bad line is reached.

Parameter Type Description
s string CSV text.
opts (optional) table {header = true, delimiter = ",", trim = false}. With header = true each row is a table keyed by the header names (empty names become column_N, duplicates get _2, _3 suffixes; short rows are padded with empty strings, extra cells are named column_N). With header = false rows are arrays of strings. trim = true strips surrounding whitespace from every cell. delimiter must be a single character.

Returns: function

Errors:

  • invalid option
  • malformed CSV while iterating (message "line N: ...")
for row in csv.rows(text) do
  total = total + tonumber(row.qty)
end

csv.encode

csv.encode(rows, opts?)

Encodes rows as CSV text with LF line endings and standard quoting.

Parameter Type Description
rows table Array of rows. Each row is an array of values, or a table keyed by column name (then opts.columns is required).
opts (optional) table {columns = {"a", "b"}, delimiter = ",", header = true}. columns selects and orders keyed-row cells and supplies the header line. For array rows a header line is written only when columns is given. Cells may be strings, numbers, booleans, decimals, nil or db.NULL (empty).

Returns: string

Errors:

  • columns missing for keyed rows
  • unsupported cell value
  • result exceeds the maximum string size (SCRIPT_LIMIT_EXCEEDED)
return csv.encode({{sku = "A1", qty = 2}}, {columns = {"sku", "qty"}})  -- "sku,qty\nA1,2\n"

data

Tabular dataset manipulation: grouping, multi-metric aggregation, pivot tables, sorting and transformations on row arrays.

Available in: ui_event, report, job.

data.group_by

data.group_by(rows, key) → table

Groups an array of rows by a key field.

Parameter Type Description
rows table Array of row tables.
key string Field name to group by.

Returns: table mapping each distinct group key value to an array of matching rows

local byRegion = data.group_by(customers, "Region")

data.aggregate

data.aggregate(rows, spec) → rows

Performs grouped rollups with multiple metric aggregations in a single pass.

Parameter Type Description
rows table Array of row tables.
spec table {by?: string|array, metrics: table}: metrics maps output column name to agg function ("count", "sum", "avg", "min", "max", "median", "first", "last") or {field, fn}.

Returns: array of aggregated row tables

local summary = data.aggregate(orders, {
  by = "Region",
  metrics = {
    order_count = "count",
    total_sales = {field = "Total", fn = "sum"},
    avg_sales = {field = "Total", fn = "avg"},
  }
})

data.summarize

data.summarize(rows, spec) → rows

Alias for data.aggregate.

Parameter Type Description
rows table Array of row tables.
spec table Aggregation specification (alias for data.aggregate).

Returns: array of aggregated row tables

local summary = data.summarize(orders, {by = "Category", metrics = {revenue = "sum"}})

data.pivot

data.pivot(rows, spec) → rows

Generates a two-dimensional pivot table from tabular rows.

Parameter Type Description
rows table Array of row tables.
spec table {row: string, col: string, value: string, agg?: string}.

Returns: array of pivot table row tables (with row key, column keys, and total)

local pt = data.pivot(sales, {row = "Region", col = "Quarter", value = "Amount", agg = "sum"})

data.sort_by

data.sort_by(rows, spec [, desc]) → rows

Sorts an array of row tables by one or multiple fields.

Parameter Type Description
rows table Array of row tables.
spec string|table Field name or array of field names (prefix with "-" for descending).
desc (optional) boolean True to sort descending (when spec is a single field name).

Returns: new array containing sorted rows

local sorted = data.sort_by(products, {"Category", "-Price"})

data.pluck

data.pluck(rows, field) → array

Extracts a single column from an array of row tables.

Parameter Type Description
rows table Array of row tables.
field string Field name to extract.

Returns: 1-indexed array of values

local ids = data.pluck(users, "id")

data.index_by

data.index_by(rows, key) → table

Indexes an array of rows by a unique key field.

Parameter Type Description
rows table Array of row tables.
key string Field name whose values serve as keys.

Returns: table mapping key values to their corresponding row tables

local userMap = data.index_by(users, "id")

data.distinct

data.distinct(rows [, field]) → array

Returns unique elements or distinct values of a field.

Parameter Type Description
rows table Array of row tables or array of scalar values.
field (optional) string Field name to extract distinct values of.

Returns: array of distinct values or deduplicated rows

local cities = data.distinct(customers, "City")

data.frequencies

data.frequencies(rows [, field]) → array

Computes frequency counts and proportions.

Parameter Type Description
rows table Array of row tables or scalar values.
field (optional) string Field name to count frequencies for.

Returns: array of {value, count, ratio} sorted descending by count

local statusCounts = data.frequencies(orders, "Status")

data.top_n

data.top_n(rows, n, field [, desc]) → rows

Returns the top n rows ranked by a field.

Parameter Type Description
rows table Array of row tables.
n number Maximum number of rows to return.
field string Field name to rank by.
desc (optional) boolean True for highest first (default true).

Returns: array of up to n row tables

local top5 = data.top_n(customers, 5, "TotalSpent")

data.filter

data.filter(rows, predicate) → rows

Filters rows using a predicate function or match table.

Parameter Type Description
rows table Array of row tables.
predicate function|table Function returning boolean or table of matching key-values.

Returns: filtered array of row tables

local active = data.filter(users, {Active = true})

data.slice

data.slice(rows, start, count) → rows

Extracts a slice of rows.

Parameter Type Description
rows table Array of row tables.
start number 1-indexed starting position.
count number Number of rows to take.

Returns: sub-array of row tables

local page1 = data.slice(rows, 1, 10)

datetime

Dates and times. A timestamp is an ISO 8601 UTC string like "2026-03-09T05:30:00.000Z"; a date is "2026-01-31". Timestamps and dates are plain strings, so they sort and compare correctly with < and ==. Calendar arithmetic and local-time parsing use the workspace time zone unless a tz argument is given. Invalid tz names raise an error.

Available in: ui_event, report, job.

datetime.now

datetime.now()

Returns the current time as an ISO UTC timestamp (millisecond precision).

Returns: string

local started = datetime.now()  -- "2026-03-09T05:30:00.123Z"

datetime.today

datetime.today(tz?)

Returns today's date (YYYY-MM-DD) in the time zone.

Parameter Type Description
tz (optional) string IANA time zone name such as "America/New_York". Defaults to the workspace time zone.

Returns: string

Errors:

  • unknown time zone
local d = datetime.today("Europe/Paris")

datetime.parse

datetime.parse(s, tz?)

Parses a date/time into an ISO UTC timestamp. On failure returns nil and an error message instead of raising.

Parameter Type Description
s string RFC 3339 (any offset), "YYYY-MM-DD" (midnight in tz), "YYYY-MM-DD HH:MM" or "YYYY-MM-DD HH:MM:SS" (local time in tz).
tz (optional) string IANA time zone name such as "America/New_York". Defaults to the workspace time zone.

Returns: string|nil, string|nil

Errors:

  • unknown time zone
  • s is not a string
local ts, err = datetime.parse("2026-03-08 21:30", "America/New_York")
if not ts then error(err) end

datetime.format

datetime.format(ts, layout, tz?)

Formats a time in the time zone. %Z is the zone abbreviation and %z the numeric offset such as +0100.

Parameter Type Description
ts string A timestamp or date (any form accepted by datetime.parse).
layout string strftime-style layout. Supported: %Y %m %d %H %M %S %y %b %B %a %A %p %I %j %Z %z %%.
tz (optional) string IANA time zone name such as "America/New_York". Defaults to the workspace time zone.

Returns: string

Errors:

  • ts cannot be parsed
  • unknown directive in layout
  • unknown time zone
  • result exceeds the maximum string size (SCRIPT_LIMIT_EXCEEDED)
return datetime.format("2026-03-09T05:30:00.000Z", "%d %b %Y %I:%M %p", "America/New_York")  -- "09 Mar 2026 01:30 AM"

datetime.add

datetime.add(ts, spec, tz?)

Adds calendar and clock amounts. years, months, days and weeks use calendar arithmetic in the time zone so local clock time is kept across daylight-saving changes; adding months clamps to the end of shorter months (Jan 31 + 1 month = Feb 28). hours, minutes and seconds are exact elapsed time. When ts is a date and only years/months/days/weeks are given the result is a date, otherwise a timestamp.

Parameter Type Description
ts string A timestamp or date.
spec table Any of years, months, days, weeks (whole numbers, may be negative) and hours, minutes, seconds (fractions allowed).
tz (optional) string IANA time zone name such as "America/New_York". Defaults to the workspace time zone.

Returns: string

Errors:

  • ts cannot be parsed
  • unknown key in spec
  • non-integer years/months/days/weeks
  • result out of range
  • unknown time zone
datetime.add("2026-03-08T06:30:00.000Z", {days = 1}, "America/New_York")  -- "2026-03-09T05:30:00.000Z"

datetime.diff

datetime.diff(a, b, unit, tz?)

Returns b - a in the unit. seconds to weeks are exact elapsed time (a day is 24 hours) and may be fractional. months and years count whole calendar months/years in the time zone, truncated toward zero.

Parameter Type Description
a string Start timestamp or date.
b string End timestamp or date.
unit string One of "seconds", "minutes", "hours", "days", "weeks", "months", "years".
tz (optional) string IANA time zone name such as "America/New_York". Defaults to the workspace time zone.

Returns: number

Errors:

  • a or b cannot be parsed
  • unknown unit
  • unknown time zone
datetime.diff("2026-01-01", "2026-03-15", "days")  -- 73

db

Read and change this workspace's tables. db.select, db.update and db.delete need no SQL, so a script that uses them runs the same on SQLite and PostgreSQL; their writes are validated and recorded in row history. db.query and db.exec take SQL in the workspace database's own dialect: reads run on a snapshot, writes need edit rights and are not recorded in row history (use records or db.update for tracked edits), and system tables, schema changes and database settings are not allowed. On PostgreSQL, SQL text cannot run inside db.transaction.

Available in: ui_event, report, job.

  • NULL (userdata): SQL NULL. Lua tables cannot hold nil, so empty field values are db.NULL.

db.query

db.query(sql, params?) → rows

Runs a read-only query and returns up to 10,000 rows.

Parameter Type Description
sql string One read-only statement; use :name placeholders.
params (optional) table Values for :name placeholders.

Returns: array of row tables keyed by column name (SQL NULL is db.NULL)

Errors:

  • SQL_NOT_ALLOWED
  • BAD_REQUEST
local rows = db.query("SELECT Name FROM Customers WHERE City = :city", {city = "Paris"})

db.query_one

db.query_one(sql, params?) → row|nil

Like db.query but returns only the first row.

Returns: first row or nil

local c = db.query_one("SELECT * FROM Customers WHERE id = :id", {id = 1})

db.query_value

db.query_value(sql, params?) → value|nil

Returns a single value.

Returns: first column of the first row, or nil when there are no rows

local n = db.query_value("SELECT count(*) FROM Orders")

db.iter

db.iter(sql, params?) → iterator

Iterates over a large result in pages of 1,000 rows (jobs only).

Available in: job.

Returns: iterator yielding row tables

for row in db.iter("SELECT * FROM Orders") do total = total + row.Qty end

db.exec

db.exec(sql, params?) → {changes, last_id}

Runs one INSERT, UPDATE or DELETE on a workspace table. Not available in reports.

Available in: ui_event, job.

Returns: table with changes and last_id

Errors:

  • SQL_NOT_ALLOWED
  • VALIDATION_FAILED
  • LINKED_RECORDS
db.exec("UPDATE Orders SET Status = 'Shipped' WHERE id = :id", {id = 7})

db.transaction

db.transaction(fn) → results of fn

Runs fn inside one write transaction (all-or-nothing). Raising an error inside fn rolls back. The block has its own time budget (250 ms in form events, 2 s in jobs).

Available in: ui_event, job.

db.transaction(function() db.exec("UPDATE Stock SET Qty = Qty - 1 WHERE id = :id", {id = 3}) records.insert("Moves", {Item = 3}) end)

db.select

db.select{from, where?, order?, limit?, offset?} → rows

Reads rows without SQL, so the script runs the same on every database. where maps fields to a value (db.NULL: empty), a list (any of its values) or operators: eq, ne, lt, le, gt, ge, contains, starts, ends, in, between, is_null; _or takes a list of where tables. order lists fields, "-" first for descending. limit is 100 unless given (at most 500).

Returns: array of row tables, as records.get returns them

Errors:

  • BAD_REQUEST
local rows = db.select{from = "Orders", where = {Status = "Open", Total = {gt = 100}}, order = {"-Ordered"}, limit = 50}

db.update

db.update{table, set, where} → {changes}

Updates every matching row as records.update would: validated, versioned and recorded in row history, all or nothing. where is required (where = {} means every row); at most 10,000 rows. Not available in reports.

Available in: ui_event, job.

Returns: table with changes

Errors:

  • VALIDATION_FAILED
  • CONCURRENT_MODIFICATION
  • BAD_REQUEST
db.update{table = "Orders", set = {Status = "Shipped"}, where = {id = 42}}

db.delete

db.delete{table, where} → {changes}

Deletes every matching row as records.delete would, all or nothing. where is required; at most 10,000 rows. Not available in reports.

Available in: ui_event, job.

Returns: table with changes

Errors:

  • LINKED_RECORDS
  • BAD_REQUEST
db.delete{table = "Drafts", where = {Status = "Abandoned"}}

db.lookup

db.lookup(table, field, where?, params?) → value|db.NULL

Access DLookup: the field's value in the first matching row, or db.NULL.

local email = db.lookup("Customers", "Email", "id = :id", {id = record.Customer})

db.count

db.count(table, field?, where?, params?) → number

Access DCount: number of matching rows (non-empty values of field, or all rows when field is "*" or omitted).

local open = db.count("Orders", "*", "Status = 'Open'")

db.sum

db.sum(table, field, where?, params?) → number|db.NULL

Access DSum.

local total = db.sum("OrderLines", "Qty", "OrderId = :id", {id = record.id})

db.avg

db.avg(table, field, where?, params?) → number|db.NULL

Access DAvg.

db.avg("Products", "Price")

db.min

db.min(table, field, where?, params?) → value|db.NULL

Access DMin.

db.min("Orders", "OrderDate")

db.max

db.max(table, field, where?, params?) → value|db.NULL

Access DMax.

db.max("Orders", "OrderDate")

db.is_null

db.is_null(v) → boolean

True for nil and db.NULL.

if db.is_null(record.ShippedAt) then … end

db.from

db.from(table) → builder

Starts a dialect-neutral query builder on table.

local rows = db.from("Orders"):where("Status", "Open"):order("-Ordered"):limit(10):all()

db.scalar

db.scalar(sql, params?) → value|nil

Runs a read query and returns a single value.

Returns: first column of the first row, or nil when there are no rows

local n = db.scalar("SELECT count(*) FROM Orders")

db.pluck

db.pluck(sql, params?) → array

Runs a read query and returns a 1-indexed list of the first column across all rows.

Returns: array of values from the first column across all rows

local ids = db.pluck("SELECT id FROM Orders WHERE Status = 'Open'")

decimal

Exact base-10 arithmetic. Currency is stored as an integer scaled by 10^4 and appears in scripts as decimal values, so decimal.new("0.1") + decimal.new("0.2") == decimal.new("0.3") is true.

Decimals support + - * / % unary minus, == < <= > >= and .. (concatenation). Arithmetic operands may be decimals, numbers (converted through their shortest exact decimal representation) or numeric strings. Lua 5.1 caveats: == is only evaluated when both sides are decimals, so decimal == number is always false; < <= > >= only work between two decimals and comparing a decimal with a number raises an error. Convert first: d == decimal.new(1), d < decimal.new("2.5"). Division keeps 16 decimal places and raises on division by zero; % follows Lua (the result has the sign of the divisor). Values are limited to about 4,900 digits and an exponent of 1000.

Decimal values have the methods d:round(places), d:tostring() and d:tonumber().

Available in: ui_event, report, job.

decimal.new

decimal.new(value)

Creates a decimal. Prefer strings for exact values; a number is converted through its shortest round-trip representation, so decimal.new(0.1) is exactly 0.1.

Parameter Type Description
value string|number|decimal A numeric string such as "12.50" or "1e3", a finite number, or another decimal.

Returns: decimal

Errors:

  • value is not a number, numeric string or decimal
  • the string is not a valid decimal or is too large
local price = decimal.new("19.99")
return price * 3  -- 59.97

decimal.is_decimal

decimal.is_decimal(value)

Returns true when value is a decimal.

Parameter Type Description
value any Any value.

Returns: boolean

return decimal.is_decimal(decimal.new(1))  -- true

event

The form event being handled.

Available in: ui_event.

  • name (string): Event name: load, current, before_insert, before_update, after_update, before_delete, after_delete, close, click, change, confirm.
  • control (string|nil): Name of the control that raised a control event.
  • old_value (any): Previous value for change/before_update control events.
  • new_value (any): New value for change/before_update control events.
  • args (table): Arguments the form was opened with (open_args).
  • answer (boolean|nil): The user's answer when re-invoked after ui.confirm.

fetch

Makes an HTTP request from a job. Only hosts and ports on the platform's egress allowlist, which platform administrators manage for all workspaces, are reachable (https unless the platform also allows http); an empty allowlist permits nothing; private and internal addresses are always refused, including after redirects (at most 5).

Available in: job.

fetch

fetch(url, {method, headers, query, params, body, json, form, bearer, basic_auth, timeout_ms, max_bytes}?) → response

Sends the request and reads the whole response.

Parameter Type Description
url string Absolute https URL.
options (optional) table method (default GET); headers (string → string); query/params (table of query parameters); body (string); json (value encoded as application/json); form (table encoded as application/x-www-form-urlencoded); bearer (string token); basic_auth ({username, password}); timeout_ms (default 10000, at most 30000); max_bytes (default 2 MiB, at most 10 MiB).

Returns: {status, ok, headers, body, json(), text(), header(name)}: ok is true for 2xx; header names are lower-case; response:json() decodes the body; response:text() returns body string; response:header(name) returns a header value case-insensitively.

Errors:

local r = fetch("https://api.example.com/rates", {params = {base = "USD"}})
if r.ok then log.info("rates", {count = #r:json().items}) end

fetch.get

fetch.get(url, options?) → response

Convenience helper for fetch(url, {method = "GET", ...}).

Parameter Type Description
url string Absolute https URL.
options (optional) table Request options (headers, query/params, bearer, etc.).

Returns: response table

local r = fetch.get("https://api.example.com/users", {params = {active = "true"}})

fetch.post

fetch.post(url, options?) → response

Convenience helper for fetch(url, {method = "POST", ...}).

Parameter Type Description
url string Absolute https URL.
options (optional) table Request options (body, json, form, headers, etc.).

Returns: response table

local r = fetch.post("https://api.example.com/items", {json = {name = "Widget", price = 19.99}})

fetch.put

fetch.put(url, options?) → response

Convenience helper for fetch(url, {method = "PUT", ...}).

Parameter Type Description
url string Absolute https URL.
options (optional) table Request options (body, json, headers, etc.).

Returns: response table

local r = fetch.put("https://api.example.com/items/12", {json = {name = "Updated Widget"}})

fetch.patch

fetch.patch(url, options?) → response

Convenience helper for fetch(url, {method = "PATCH", ...}).

Parameter Type Description
url string Absolute https URL.
options (optional) table Request options (body, json, headers, etc.).

Returns: response table

local r = fetch.patch("https://api.example.com/items/12", {json = {price = 24.99}})

fetch.delete

fetch.delete(url, options?) → response

Convenience helper for fetch(url, {method = "DELETE", ...}).

Parameter Type Description
url string Absolute https URL.
options (optional) table Request options (headers, query/params, etc.).

Returns: response table

local r = fetch.delete("https://api.example.com/items/12")

files

File attachments. A file is identified by its id, the value stored in an attachments field. Content is read and written as a Lua string; reads count against the run's memory budget.

Available in: ui_event, report, job.

files.info

files.info(id) → info|nil

Describes a file without reading it.

Parameter Type Description
id string A file id, as stored in an attachments field.

Returns: {id, name, size, content_type, sha256, width, height, state, created_at} (width and height only for images; state is "pending" or "attached"), or nil when no such file exists in this workspace.

local f = files.info(record.Photos[1])
if f then log.info(f.name, {size = f.size}) end

files.read

files.read(id) → content|nil

Reads a whole file. Use it for small text or data files; images and documents are meant to be shown to people, not parsed in Lua.

Parameter Type Description
id string A file id.

Returns: The file's bytes as a string, or nil when no such file exists.

Errors:

  • BAD_REQUEST: the file is larger than this context allows (1 MiB in form events and reports, 10 MiB in jobs)
  • SCRIPT_LIMIT_EXCEEDED: the run has read more than its memory budget
local text = files.read(id)
local rows = csv.parse(text)

files.create

files.create(name, content, content_type?) → info

Stores a new file.

Available in: job.

Parameter Type Description
name string The file name people will see; any directory part is dropped.
content string The bytes (at most the server's file size limit, 25 MiB by default).
content_type (optional) string Accepted for readability and ignored: the type is detected from the content.

Returns: The new file's info table. The file is pending until a row refers to it: put info.id into an attachments field within 24 hours.

Errors:

local f = files.create("totals.csv", csv.encode(rows, {columns = {"id", "total"}}))
records.update("Reports", report_id, {Attachment = {f.id}})

geo

Locations and areas. Use these with geo_point and geo_area fields: records.get returns their values as geometry tables and records.insert and records.update accept them.

A point is a table {type = "Point", coordinates = {longitude, latitude}} and an area is a GeoJSON Polygon or MultiPolygon table. Everywhere a point is expected you can also pass {lat = .., lon = ..}, the text "lat, lon", or a GeoJSON Feature; everywhere an area is expected, a Feature or GeoJSON text. Coordinates are rounded to 7 decimal places. Areas that cross the 180° meridian are not supported.

Distances are in metres along the Earth's surface (haversine) and areas in square metres (spherical approximation).

Available in: ui_event, report, job.

geo.point

geo.point(lat, lon, alt?)

Builds a point. Note the argument order: latitude first, as people write coordinates; the stored coordinates are [longitude, latitude] as GeoJSON requires.

Parameter Type Description
lat number Latitude in degrees, -90 to 90.
lon number Longitude in degrees, -180 to 180.
alt (optional) number Altitude in metres, kept as the third coordinate.

Returns: table

Errors:

  • lat or lon is not a number
  • lat is outside -90 to 90 or lon is outside -180 to 180
local p = geo.point(51.50135, -0.14189)
return p.coordinates[1]  -- -0.14189 (the longitude)

geo.lat

geo.lat(point)

Returns a point's latitude.

Parameter Type Description
point table|string A point.

Returns: number

Errors:

  • point is not a valid point
return geo.lat("51.5, -0.14")  -- 51.5

geo.lon

geo.lon(point)

Returns a point's longitude.

Parameter Type Description
point table|string A point.

Returns: number

Errors:

  • point is not a valid point
return geo.lon("51.5, -0.14")  -- -0.14

geo.distance

geo.distance(a, b)

The great-circle distance between two points, in metres. It is symmetric: distance(a, b) equals distance(b, a).

Parameter Type Description
a table|string A point.
b table|string A point.

Returns: number

Errors:

  • a or b is not a valid point
return geo.distance(geo.point(51.5074, -0.1278), geo.point(48.8566, 2.3522))  -- about 343,500

geo.within

geo.within(point, area)

Tests whether a point is inside an area. Holes are outside, and a point exactly on the boundary counts as inside.

Parameter Type Description
point table|string A point.
area table|string A Polygon or MultiPolygon.

Returns: boolean

Errors:

  • point is not a valid point
  • area is not a valid area
local area = records.get("Zones", 1).Fence
return geo.within(geo.point(5, 5), area)

geo.area

geo.area(area)

The area in square metres (holes are subtracted).

Parameter Type Description
area table|string A Polygon or MultiPolygon.

Returns: number

Errors:

  • area is not a valid area
return geo.area(records.get("Zones", 1).Fence) / 10000  -- hectares

geo.centroid

geo.centroid(area)

The area-weighted centre of an area, as a point. For a concave area it can lie outside the area.

Parameter Type Description
area table|string A Polygon or MultiPolygon.

Returns: table

Errors:

  • area is not a valid area
return geo.centroid(records.get("Zones", 1).Fence)

geo.bbox

geo.bbox(area)

The bounding box as a list {minLon, minLat, maxLon, maxLat}.

Parameter Type Description
area table|string A Polygon or MultiPolygon.

Returns: table

Errors:

  • area is not a valid area
local b = geo.bbox(records.get("Zones", 1).Fence)
return b[3] - b[1]  -- width in degrees

job

The running job.

Available in: job.

  • id (string): Job id.
  • name (string): Job name.
  • run_id (string): This run's id.
  • args (table): The job's args, with any enqueue args merged over them.

jobs

Starts background jobs.

Available in: ui_event, job.

jobs.enqueue

jobs.enqueue(name, args?) → run_id

Queues a run of a job. From a form, the user must be an editor or above and the job's args must contain allow_enqueue = true.

Parameter Type Description
name string Job name.
args (optional) table Arguments merged over the job's own args (string keys; JSON-compatible values).

Returns: The new run's id. The run starts as soon as a worker is free, following the job's overlap policy.

Errors:

local run = jobs.enqueue("Send invoices", {customer = record.id})

json

JSON encoding and decoding.

Encoding rules: tables with keys exactly 1..n (n >= 1) or marked with json.array become arrays; an empty unmarked table becomes {}; other tables become objects with sorted keys (keys must be strings or numbers). json.null and db.NULL become null; decimals become JSON strings. Integral numbers within +/-2^53 are written without a decimal point; NaN and infinity raise. Nesting deeper than 100 levels and cycles raise.

Decoding maps objects to tables, arrays to tables marked as arrays (so they re-encode as arrays even when empty), null to json.null, and integers beyond +/-2^53 to strings to avoid precision loss.

json.array and json.object mark a table by giving it a private metatable. Calling setmetatable on a marked table replaces the metatable and drops the mark.

Available in: ui_event, report, job.

  • null (userdata): The JSON null value. Compare with ==; it is distinct from nil and db.NULL but both encode as null.

json.encode

json.encode(value, opts?)

Encodes a Lua value as JSON text.

Parameter Type Description
value any The value to encode.
opts (optional) table {pretty = boolean}: pretty = true indents with two spaces.

Returns: string

Errors:

  • unsupported value type (function, thread, non-null userdata)
  • NaN or infinity
  • table keys that are not strings or numbers
  • nesting deeper than 100 or cyclic tables
  • result exceeds the maximum string size (SCRIPT_LIMIT_EXCEEDED)
return json.encode({name = "Ada", tags = json.array({})})  -- {"name":"Ada","tags":[]}

json.decode

json.decode(s)

Parses JSON text. On invalid JSON returns nil and an error message instead of raising. A literal null decodes to json.null (not nil).

Parameter Type Description
s string JSON text.

Returns: any|nil, string|nil

local v, err = json.decode('{"a":[1,2,null]}')
if not v then error(err) end

json.array

json.array(t?)

Marks t (and returns it) so it always encodes as a JSON array, even when empty. Keys other than 1..n make encoding fail.

Parameter Type Description
t (optional) table Table to mark; a new empty table when omitted.

Returns: table

Errors:

  • t has a protected metatable
local rows = json.array({})

json.object

json.object(t?)

Marks t (and returns it) so it always encodes as a JSON object, including an empty table or one with integer keys. Overrides json.array.

Parameter Type Description
t (optional) table Table to mark; a new empty table when omitted.

Returns: table

Errors:

  • t has a protected metatable
return json.encode(json.object({}))  -- {}

log

Writes to the run log (the script console, and the job run log for jobs). Messages longer than 8 KiB are truncated with an ellipsis; fields are limited to about 8 KiB when serialized. The most recent 256 KiB of log output is kept per run.

Available in: ui_event, report, job.

log.debug

log.debug(msg, fields?)

Writes a debug entry to the run log.

Parameter Type Description
msg any Message; converted with tostring.
fields (optional) table Structured context: a table with string keys. Values may be strings, numbers, booleans, decimals, json.null or nested tables.

Errors:

  • fields is not a table with string keys
  • fields are too large
log.debug("imported rows", {count = 12, source = "upload"})

log.info

log.info(msg, fields?)

Writes a info entry to the run log.

Parameter Type Description
msg any Message; converted with tostring.
fields (optional) table Structured context: a table with string keys. Values may be strings, numbers, booleans, decimals, json.null or nested tables.

Errors:

  • fields is not a table with string keys
  • fields are too large
log.info("imported rows", {count = 12, source = "upload"})

log.warn

log.warn(msg, fields?)

Writes a warn entry to the run log.

Parameter Type Description
msg any Message; converted with tostring.
fields (optional) table Structured context: a table with string keys. Values may be strings, numbers, booleans, decimals, json.null or nested tables.

Errors:

  • fields is not a table with string keys
  • fields are too large
log.warn("imported rows", {count = 12, source = "upload"})

log.error

log.error(msg, fields?)

Writes a error entry to the run log.

Parameter Type Description
msg any Message; converted with tostring.
fields (optional) table Structured context: a table with string keys. Values may be strings, numbers, booleans, decimals, json.null or nested tables.

Errors:

  • fields is not a table with string keys
  • fields are too large
log.error("imported rows", {count = 12, source = "upload"})

notify

Show messages to the user.

Available in: ui_event.

notify.toast

notify.toast(text, {level?})

A brief message; level is "info" (default), "success", "warning" or "error".

notify.toast("Order saved", {level = "success"})

notify.alert

notify.alert(text, {title?})

A message the user must dismiss.

notify.alert("Stock is low", {title = "Heads up"})

record

The form's current record buffer. Read and assign fields by name (record.Qty = 5); assignments are sent back to the form and saved with the record.

Available in: ui_event.

  • id (number|nil): Row id (nil for a new record).
  • version (number|nil): Row _version the form loaded.
  • is_new (boolean): True for a record not yet saved.
  • is_dirty (boolean): True when the user changed fields.

record.get

record.get(field) → value

Reads a field (db.NULL when empty).

local q = record.get("Qty")

record.set

record.set(field, value)

Assigns a field.

record.set("Status", "Approved")

record.changed_fields

record.changed_fields() → fields

Returns a list of fields assigned during this event.

local fields = record.changed_fields()

record.reset

record.reset(field?)

Discards changes to a field or the entire record buffer.

record.reset("Status")

record.to_table

record.to_table() → table

Returns a snapshot of all current record values as a plain Lua table.

local snapshot = record.to_table()

records

Read and change rows field by field, with the same validation, concurrency checks and history as the datasheet. Fields are named by their field (physical) names.

Available in: ui_event, report, job.

records.get

records.get(table, id) → row|nil

Reads one row by id.

Returns: row table (id, _version and every field; currency as decimal, yes/no as boolean, empty as db.NULL) or nil

local c = records.get("Customers", record.Customer)

records.find_one

records.find_one(table, where) → row|nil

Finds the first row matching the where filter.

Returns: first matching row table or nil

local u = records.find_one("Customers", {Email = "alice@example.com"})

records.count

records.count(table, where?) → number

Counts rows matching the where filter (or all rows if where is omitted).

Returns: integer count of matching rows

local n = records.count("Orders", {Status = "Open"})

records.exists

records.exists(table, where?) → boolean

Reports whether any rows match the where filter.

Returns: true if at least one matching row exists

if records.exists("Customers", {City = "Paris"}) then … end

records.insert

records.insert(table, values) → id

Creates a row and returns its id.

Available in: ui_event, job.

Errors:

  • REQUIRED_FIELD
  • VALIDATION_FAILED
  • DUPLICATE_VALUE
  • LINKED_RECORDS
local id = records.insert("Notes", {Text = "Called", Customer = record.id})

records.insert_many

records.insert_many(table, rows) → ids

Creates multiple rows inside a transaction and returns an array of their ids.

Available in: ui_event, job.

Errors:

  • REQUIRED_FIELD
  • VALIDATION_FAILED
  • DUPLICATE_VALUE
  • LINKED_RECORDS
local ids = records.insert_many("Items", {{Name = "A"}, {Name = "B"}})

records.update

records.update(table, id, values, {version?}) → version

Changes fields of a row. With version, fails if the row changed since that version; without it, applies to the current version.

Available in: ui_event, job.

Errors:

  • CONCURRENT_MODIFICATION
  • VALIDATION_FAILED
  • NOT_FOUND
records.update("Orders", id, {Status = "Shipped"})

records.update_many

records.update_many(table, where, values) → {changes}

Updates every row matching where with the given field values.

Available in: ui_event, job.

Errors:

  • VALIDATION_FAILED
  • BAD_REQUEST
records.update_many("Orders", {Status = "Open"}, {Status = "Processing"})

records.upsert

records.upsert(table, match, values) → id

Finds a row matching match and updates it with values, or inserts a new row merging match and values.

Available in: ui_event, job.

Errors:

  • VALIDATION_FAILED
  • REQUIRED_FIELD
records.upsert("Counters", {Key = "views"}, {Count = 1})

records.delete

records.delete(table, id, {version?})

Deletes a row.

Available in: ui_event, job.

Errors:

  • CONCURRENT_MODIFICATION
  • LINKED_RECORDS
  • NOT_FOUND
records.delete("Drafts", id)

reports

Runs reports from a job and saves the result as a run artifact.

Available in: job.

reports.generate

reports.generate(report, params?, options?) → {name, size, rows, format}

Runs the report as the job (with an editor's data rights) and saves a rendering as an artifact of this run, within the artifact quota. csv holds the detail rows with one leading column per grouping level; json mirrors the groups as a tree with their header and footer values; html is a self-contained table. Maps and images are not part of generated files. Saving the same name again replaces the file.

Parameter Type Description
report string The report's id or name.
params (optional) table Values for the parameters of the report's saved-query source.
options (optional) table format: "csv" (default), "json" or "html". name: the artifact file name (default: the report's name with the format's extension).

Returns: The saved artifact's name and size, the number of detail rows and the format.

Errors:

  • NOT_FOUND: no such report
  • BAD_REQUEST: unknown format, invalid name or a missing report parameter
  • QUOTA_EXCEEDED: artifact storage full or too many artifacts in this run
local out = reports.generate("Monthly sales", {month = "2026-09"}, {format = "csv", name = "sales-2026-09.csv"})

staging

Scratch tables for bulk loads. staging.create returns a handle with these methods; the staging tables are private to the run and deleted when it ends (at most 8 per run, 1,000,000 rows and 256 MiB each).

handle:insert(row): adds one row, given as a table keyed by column name or as an array in column order. Values are text, numbers, booleans or db.NULL; missing columns are empty.

handle:query(sql, params?) → rows: runs one SELECT against the table, which is named data (columns are the names given to create, plus n, the insertion number). Use ? placeholders with an array or :name placeholders with a table. Writes, PRAGMA, ATTACH and any other table are refused (SQL_NOT_ALLOWED). Returns an array of row tables keyed by column name; SQL NULL is db.NULL. A result of more than 100,000 rows or 32 MiB is an error.

handle:count() → number: the number of rows inserted.

handle:merge_into(table_name, {mode, key, chunk, all_or_nothing}?) → {inserted, updated, skipped, error_count, errors}: copies every row into the table. Staging column names must match the table's field names (display or physical name, any case). mode is "append" (default), "upsert" (key = the name of a field with a unique index; existing rows with the same key are updated) or "replace" (delete all rows, then insert; one transaction up to 50,000 rows). Rows are converted exactly like row API values (text "12.50" for currency, ISO dates, yes/no words) and written in chunks of chunk rows (default 2,000, at most 10,000) so interactive saves are served between chunks; rows the database rejects are skipped and listed in errors (the first 100, each {row, column, code, message}). With all_or_nothing = true the whole merge is one transaction that rolls back on the first bad row and raises an error. merge_into cannot run inside db.transaction.

Available in: job.

staging.create

staging.create({columns = {...}}) → handle

Creates an empty staging table for this run.

Parameter Type Description
options table columns: array of column names (letters, digits, underscores; at most 256; n is reserved).

Returns: A staging handle with the methods insert, query, count and merge_into (see the module description).

Errors:

local st = staging.create({columns = {"sku", "qty"}})
st:insert({sku = "A-1", qty = 5})
st:insert({"B-2", 7})
local rows = st:query("SELECT sku FROM data WHERE qty > :min", {min = 5})
local res = st:merge_into("Stock", {mode = "upsert", key = "sku"})
log.info("merged", {inserted = res.inserted, updated = res.updated})

stats

Descriptive statistics, linear regression, time-series moving averages and distribution analysis. All functions accept either an array of numbers or (rows, field_name).

Available in: ui_event, report, job.

stats.sum

stats.sum(xs [, field]) → number|nil

Calculates the sum of numbers.

Parameter Type Description
xs table Array of numbers or array of row tables.
field (optional) string Field name if xs is an array of row tables.

Returns: sum of elements, or nil if empty

local total = stats.sum({10, 20, 30})

stats.mean

stats.mean(xs [, field]) → number|nil

Calculates the arithmetic mean.

Parameter Type Description
xs table Array of numbers or array of row tables.
field (optional) string Field name if xs is an array of row tables.

Returns: arithmetic mean (average), or nil if empty

local avg = stats.mean(orders, "Total")

stats.median

stats.median(xs [, field]) → number|nil

Calculates the median (50th percentile).

Parameter Type Description
xs table Array of numbers or array of row tables.
field (optional) string Field name if xs is an array of row tables.

Returns: median value, or nil if empty

local med = stats.median({1, 3, 3, 6, 7, 8, 9})

stats.mode

stats.mode(xs [, field]) → number|nil

Finds the mode (most common value).

Parameter Type Description
xs table Array of numbers or array of row tables.
field (optional) string Field name if xs is an array of row tables.

Returns: most frequent value, or nil if empty

local m = stats.mode({1, 2, 2, 3})

stats.min

stats.min(xs [, field]) → number|nil

Finds the minimum value.

Parameter Type Description
xs table Array of numbers or array of row tables.
field (optional) string Field name if xs is an array of row tables.

Returns: minimum value, or nil if empty

local low = stats.min(rates)

stats.max

stats.max(xs [, field]) → number|nil

Finds the maximum value.

Parameter Type Description
xs table Array of numbers or array of row tables.
field (optional) string Field name if xs is an array of row tables.

Returns: maximum value, or nil if empty

local high = stats.max(rates)

stats.range

stats.range(xs [, field]) → number|nil

Calculates the range (max - min).

Parameter Type Description
xs table Array of numbers or array of row tables.
field (optional) string Field name if xs is an array of row tables.

Returns: difference between max and min, or nil if empty

local span = stats.range({10, 2, 8})

stats.variance

stats.variance(xs [, field]) → number|nil

Calculates the unbiased sample variance.

Parameter Type Description
xs table Array of numbers or array of row tables.
field (optional) string Field name if xs is an array of row tables.

Returns: sample variance (n-1 denominator), or nil if count < 2

local v = stats.variance({2, 4, 4, 4, 5, 5, 7, 9})

stats.pvariance

stats.pvariance(xs [, field]) → number|nil

Calculates the population variance.

Parameter Type Description
xs table Array of numbers or array of row tables.
field (optional) string Field name if xs is an array of row tables.

Returns: population variance (n denominator), or nil if empty

local pv = stats.pvariance({2, 4, 4, 4, 5, 5, 7, 9})

stats.stdev

stats.stdev(xs [, field]) → number|nil

Calculates the sample standard deviation.

Parameter Type Description
xs table Array of numbers or array of row tables.
field (optional) string Field name if xs is an array of row tables.

Returns: sample standard deviation, or nil if count < 2

local s = stats.stdev(sales, "Amount")

stats.pstdev

stats.pstdev(xs [, field]) → number|nil

Calculates the population standard deviation.

Parameter Type Description
xs table Array of numbers or array of row tables.
field (optional) string Field name if xs is an array of row tables.

Returns: population standard deviation, or nil if empty

local ps = stats.pstdev(populationScores)

stats.percentile

stats.percentile(xs, p [, field]) → number|nil

Calculates the p-th percentile using linear interpolation.

Parameter Type Description
xs table Array of numbers or array of row tables.
p number Percentile (0 to 100, or 0.0 to 1.0).
field (optional) string Field name if xs is an array of row tables.

Returns: interpolated percentile value, or nil if empty

local p95 = stats.percentile(latencies, 95)

stats.quantiles

stats.quantiles(xs, probs [, field]) → table

Calculates multiple percentiles in a single pass.

Parameter Type Description
xs table Array of numbers or array of row tables.
probs table Array of probabilities (e.g. {0.25, 0.5, 0.75}).
field (optional) string Field name if xs is an array of row tables.

Returns: array of quantile values

local q = stats.quantiles(scores, {0.25, 0.5, 0.75})

stats.iqr

stats.iqr(xs [, field]) → number|nil

Calculates the interquartile range.

Parameter Type Description
xs table Array of numbers or array of row tables.
field (optional) string Field name if xs is an array of row tables.

Returns: interquartile range (Q75 - Q25), or nil if empty

local spread = stats.iqr(prices)

stats.summary

stats.summary(xs [, field]) → table|nil

Returns a comprehensive summary table of descriptive statistics.

Parameter Type Description
xs table Array of numbers or array of row tables.
field (optional) string Field name if xs is an array of row tables.

Returns: {count, sum, mean, stdev, min, q25, median, q75, max, iqr} or nil if empty

local s = stats.summary(orders, "Total")
log.info("stats", {avg = s.mean, median = s.median, iqr = s.iqr})

stats.zscores

stats.zscores(xs [, field]) → table

Calculates the z-score for each value.

Parameter Type Description
xs table Array of numbers or array of row tables.
field (optional) string Field name if xs is an array of row tables.

Returns: array of standardized z-scores ((x - mean) / stdev)

local z = stats.zscores({10, 20, 30, 40, 50})

stats.correl

stats.correl(xs, ys) → number|nil

Calculates the Pearson product-moment correlation coefficient.

Parameter Type Description
xs table First array of numbers.
ys table Second array of numbers.

Returns: Pearson correlation coefficient r (-1.0 to 1.0), or nil if undefined

local r = stats.correl(adSpend, revenue)

stats.covariance

stats.covariance(xs, ys) → number|nil

Calculates the sample covariance between two variables.

Parameter Type Description
xs table First array of numbers.
ys table Second array of numbers.

Returns: sample covariance, or nil if count < 2

local cov = stats.covariance(xs, ys)

stats.linreg

stats.linreg(xs, ys) → table|nil

Calculates ordinary least-squares linear regression (y = slope * x + intercept).

Parameter Type Description
xs table Independent variable values (x).
ys table Dependent variable values (y).

Returns: {slope, intercept, r, r2} or nil if insufficient points

local fit = stats.linreg(x, y)
local forecast = fit.slope * nextX + fit.intercept

stats.moving_avg

stats.moving_avg(xs, window) → table

Calculates the simple moving average over a sliding window.

Parameter Type Description
xs table Array of numbers.
window number Window size in elements.

Returns: array of simple moving averages of length (#xs - window + 1)

local sma = stats.moving_avg(dailyRevenue, 7)

stats.ema

stats.ema(xs, alpha) → table

Calculates the exponential moving average (EMA).

Parameter Type Description
xs table Array of numbers.
alpha number Smoothing factor between 0.0 and 1.0.

Returns: array of exponential moving averages of same length as xs

local ema = stats.ema(prices, 0.2)

stats.cumsum

stats.cumsum(xs) → table

Calculates the cumulative sum.

Parameter Type Description
xs table Array of numbers.

Returns: array of cumulative sums

local running = stats.cumsum({1, 2, 3, 4}) -- {1, 3, 6, 10}

stats.diffs

stats.diffs(xs) → table

Calculates discrete differences between consecutive elements.

Parameter Type Description
xs table Array of numbers.

Returns: array of differences x[i] - x[i-1]

local d = stats.diffs({10, 15, 12}) -- {5, -3}

stats.pct_change

stats.pct_change(xs) → table

Calculates percentage changes between consecutive elements.

Parameter Type Description
xs table Array of numbers.

Returns: array of percentage changes (x[i] - x[i-1]) / x[i-1]

local p = stats.pct_change({100, 110, 99}) -- {0.1, -0.1}

stats.histogram

stats.histogram(xs, bins [, field]) → table

Computes equal-width frequency bins across the dataset's range.

Parameter Type Description
xs table Array of numbers or array of row tables.
bins number Number of equal-width bins (at least 1, at most 10000).
field (optional) string Field name if xs is an array of row tables.

Returns: array of bin tables: { {min, max, count, ratio}, ... }

local bins = stats.histogram(ages, 5)

ui

Change the form: control properties, focus, navigation and confirmations.

Available in: ui_event.

ui.control

ui.control(name) → props

A snapshot of a control's properties (visible, enabled, locked, caption, value…).

if ui.control("Approve").visible then … end

ui.set

ui.set(name, props)

Changes control properties: visible, enabled, locked, required (booleans), caption, tooltip, error (text), tone ("default"|"warning"|"danger"|"success"), value (unbound controls).

ui.set("Approve", {visible = record.Total > 5000})

ui.focus

ui.focus(name)

Moves focus to a control.

ui.focus("Qty")

ui.goto_record

ui.goto_record(target)

Moves to a record: an id, or "next", "prev", "first", "last", "new".

ui.goto_record("new")

ui.open_form

ui.open_form(name, {where?, args?})

Opens another form, optionally filtered with a filter-model table and passing args.

ui.open_form("Customer", {args = {id = record.Customer}})

ui.open_report

ui.open_report(name, {params?})

Opens a report.

ui.open_report("Invoice", {params = {order_id = record.id}})

ui.open_url

ui.open_url(url, target?)

Opens a URL in the browser: an http, https or mailto link, or a path on this server (/...). target is a window name such as _blank (default) or _self.

ui.open_url("https://example.com/docs", "_blank")

ui.close

ui.close()

Closes the form.

ui.close()

ui.requery

ui.requery(control?)

Reloads the form's records, or one subform/combo box.

ui.requery("Lines")

ui.refresh

ui.refresh()

Alias for ui.requery(); reloads the form's record source.

ui.refresh()

ui.cancel

ui.cancel(message)

Cancels the pending action (save, insert, delete) in before_* events and shows message.

ui.cancel("Orders over 5,000 need an approver.")

ui.confirm

ui.confirm(message, continuation)

Asks the user to confirm. When answered, the form calls the handler function named continuation with event.answer set.

ui.confirm("Delete all lines?", "on_delete_lines")

ui.set_error

ui.set_error(control, message)

Sets an inline validation error message on a control.

ui.set_error("Email", "Invalid email address format.")

ui.clear_error

ui.clear_error(control)

Clears any inline validation error on a control.

ui.clear_error("Email")

ui.clear_errors

ui.clear_errors()

Clears inline validation errors from all controls on the form.

ui.clear_errors()

ui.set_visible

ui.set_visible(control, visible)

Shows or hides a control.

ui.set_visible("ShippingAddress", record.NeedsDelivery)

ui.set_enabled

ui.set_enabled(control, enabled)

Enables or disables a control.

ui.set_enabled("SubmitBtn", record.is_dirty)

ui.set_read_only

ui.set_read_only(control, read_only)

Locks or unlocks a control for editing.

ui.set_read_only("UnitPrice", not user.is_admin)

ui.set_locked

ui.set_locked(control, locked)

Locks or unlocks a control (alias for set_read_only).

ui.set_locked("Qty", true)

ui.set_required

ui.set_required(control, required)

Marks a control as required or optional.

ui.set_required("Reason", record.Status == "Rejected")

ui.set_label

ui.set_label(control, text)

Sets the caption or label text of a control.

ui.set_label("SaveBtn", "Update Record")

ui.set_caption

ui.set_caption(control, text)

Sets the caption text of a control (alias for set_label).

ui.set_caption("Title", "Edit Mode")

ui.set_value

ui.set_value(control, value)

Sets the value of an unbound control.

ui.set_value("SearchBox", "")

url

Pure URL parsing, building and percent-encoding helpers. Evaluated entirely in-memory with zero network or DNS lookups.

Available in: ui_event, report, job.

url.parse

url.parse(str) → table|nil, err

Parses a URL string into its components.

Parameter Type Description
str string URL string to parse.

Returns: table with scheme, host, hostname, port, path, query, raw_query, fragment (or nil, err)

local u = url.parse("https://api.example.com:8443/v1/items?tag=new#section")
log.info("parsed", {host = u.hostname, port = u.port, tag = u.query.tag})

url.build

url.build(components) → string

Constructs a URL string from component parts.

Parameter Type Description
components table Table with scheme, host (or hostname, port), path, query or raw_query, fragment.

Returns: reconstructed URL string

local s = url.build({scheme = "https", host = "api.example.com", path = "/v1/search", query = {q = "widgets"}})

url.encode

url.encode(str) → string

Percent-encodes a string according to RFC 3986.

Parameter Type Description
str string Text to percent-encode for query/path.

Returns: percent-encoded string

local enc = url.encode("hello world & goodbye")

url.decode

url.decode(str) → string|nil, err

Decodes a percent-encoded string.

Parameter Type Description
str string Percent-encoded string to decode.

Returns: decoded string (or nil, error on malformed escapes)

local dec = url.decode("hello%20world")

url.query_encode

url.query_encode(params) → string

Encodes a table of parameters into a URL query string, sorted stably by key.

Parameter Type Description
params table Table mapping query parameter keys to scalar values or arrays.

Returns: encoded query string (without leading '?')

local qs = url.query_encode({page = 1, sort = "desc", tags = {"alpha", "beta"}})

url.query_decode

url.query_decode(str) → table

Decodes a URL query string into a Lua table.

Parameter Type Description
str string Query string with or without leading '?'.

Returns: table of query parameters (lists for multi-value keys)

local params = url.query_decode("page=1&sort=desc")

workspace

Information about the workspace and the user the script runs as.

Available in: ui_event, report, job.

  • timezone (string): The workspace's IANA time zone.
  • user (table): {id, email, display_name, role} of the user (jobs run as the job principal).