Skip to content

Steel language reference

Table scripts use a fail-closed allowlist. The server macro-expands the program with Steel’s own parser and rejects every form or identifier that is not explicitly accepted.

A script must be a non-empty S-expression beginning with (. Steel validates balanced syntax. Literals, comments, quoting, if, begin, and, or, let, lambdas, and immutable vectors are supported when their contents also pass the allowlist.

The last expression supplies the target value. A target must ultimately receive a string, number, integer, boolean, or correctly typed money result.

Function Purpose
steel_get_column name column Read one current-row or linked-row column
steel_related_aggregate operation table column anchor Aggregate a related collection
steel_related_count_rows table anchor Count related rows
steel_related_exists table anchor Test for related rows
has-var? name Test whether a key exists in the active row snapshot
quantity-add link column amount Record a positive quantity-ledger contribution
quantity-subtract link column amount Record a negative quantity-ledger contribution

All database names and operations must be string literals. These functions read request context prepared outside the VM; they do not issue unrestricted queries themselves.

Quantity contributions are explained in Quantity ledger.

Source math forms are transformed to exact-decimal operations before the script is stored:

+ - * / ^ ** pow sqrt
> < = >= <= min max abs round
ln log log10 exp sin cos tan

Direct decimal functions are also available:

to-decimal
decimal-add decimal-sub decimal-mul decimal-div
decimal-abs decimal-round decimal-min decimal-max
decimal-eq decimal-gt decimal-gte decimal-lt decimal-lte
decimal-zero decimal-one decimal-pi decimal-e
decimal-exp decimal-ln decimal-log10 decimal-pow decimal-sqrt
decimal-sin decimal-cos decimal-tan
decimal-compound decimal-percentage decimal-format
set-precision clear-precision get-precision

Per-script precision changes are cleared after execution and cannot leak into another request.

Columns with these kinds cannot participate in math:

BIGINT TEXT BOOLEAN
PHONE and phone component types
IBAN and IBAN component types
CREDIT_CARD
DATE TIME INSTANT USER_DATETIME RAW_DATETIME
DURATION PERIOD TIMESTAMPTZ TIMESTAMP

Money is a currency-aware runtime value. Use explicit functions:

money? money-new
money-add money-sub money-mul money-div
money-abs money-amount
money-eq money-gt money-gte money-lt money-lte

Rules:

  • money-new requires an uppercase ISO currency and a decimal amount;
  • addition, subtraction, and comparison require matching currencies;
  • multiplication and division take a non-money decimal scalar;
  • money-amount explicitly extracts an amount for decimal operations;
  • every money-returning branch must return the same currency;
  • a MONEY target must receive its configured currency;
  • a non-money target cannot receive a money value.
(money-add
(money-new "EUR" "10.00")
(money-new "EUR" "2.50"))

See How money works for storage and rounding rules.

Numbers and predicates
quotient remainder modulo expt
exact->inexact inexact->exact floor ceiling truncate
number? complex? real? rational? int? integer? exact-integer?
float? nan? positive? negative? zero? even? odd?
exact? inexact? finite? infinite?
boolean? not eq? eqv? equal?
Strings and characters
string? string string-append to-string
string-length utf8-length substring string-ref
string=? string-ci=? string<? string-ci<?
string<=? string-ci<=? string>? string-ci>?
string>=? string-ci>=?
string->list string->vector string->bytes string->number string->int
number->string int->string string->symbol symbol->string
string-upcase string-downcase string-foldcase
starts-with? ends-with? string-contains? string-replace string-push
make-string split-whitespace split-once split-many string-join
trim trim-start trim-end trim-start-matches trim-end-matches
char-upcase char-downcase char-foldcase char-digit? char-whitespace?
char->number char->integer integer->char char=? char-ci=?
Lists, immutable vectors, and hashes
list list? pair? null? car cdr cons length list-ref
append reverse member memq list-contains second third
take list-tail list-drop try-list-ref list-chunks list->string
list->vector empty?
immutable-vector make-immutable-vector vector-ref vector-length
immutable-vector->list immutable-vector->string immutable-vector-copy
immutable-vector-append immutable-vector-push immutable-vector-rest
immutable-vector-take immutable-vector-drop
hash hash-ref hash-try-get hash-length hash-contains? hash-empty?
hash-insert hash-remove hash-clear hash-union

The hash functions above return new values; mutation is not enabled.

Higher-order helpers
map filter foldl fold reduce apply for-each range
first rest last list-sort

Functions passed to a higher-order helper must themselves be allowed.

Common supported targets include TEXT, BOOLEAN, INT, DECIMAL, and MONEY.

A script cannot target:

  • system columns: id, deleted, created_at, or row_revision;
  • a quantity-ledger-backed column;
  • an accounting-transfer source column;
  • BIGINT;
  • phone, IBAN, or credit-card types;
  • DATE, TIME, INSTANT, datetime, duration, period, timestamp, or timestamptz types.

BIGINT is also prohibited as a referenced input. Other non-math types may be read for supported value operations but cannot be passed through decimal arithmetic.

The following forms illustrate behavior that is not permitted:

steel_query_sql
define define-syntax define-values
set! set-car! set-cdr! set-box! box-set!
vector-set! mutable-vector-set! string-set! bytes-set!
hash-set! hash-remove!
eval load require return

Unknown functions are rejected as well. Scripts cannot import code, evaluate generated code, change global bindings, mutate collections, access undeclared database data, or retain state between requests.