Script Fields
A Script field is a virtual column whose value is the return value of a short PHPScript snippet. Like a Calculation field it stores nothing and can't be edited — but instead of a SQL formula it runs real code, so it can query related records, call string and date functions, and build formatted output.
Use a script field when a Calculation can't express what you need: multi-step logic, a lookup that a token can't reach, or a small piece of HTML.
Adding a script field
In a table's Configure screen, open the Fields tab, click Add Field, and choose Script. Give it a label, then write the snippet in the code editor. Pick a Render mode (Text or HTML) and save.
The snippet must return a single value, which is cast to a string and shown in the cell. Returning nothing — or a non-scalar like an array — shows an empty cell.
return "Hi " . $record["first_name"];
When it runs
A script field is evaluated lazily, once per record, when you open the record detail screen — not in grids, lists, or API query results (there it shows blank). Each field runs on its own, so one slow or failing field never blocks the others, and a throwing snippet renders an inline error in just that cell without failing the record.
Each evaluation is capped at 5 seconds. Keep queries cheap.
What the snippet can use
| Variable | What it holds |
|---|---|
$record |
This record's values, keyed by field id (the SQL column) — e.g. $record["email"], $record["amount"]. Plus $record["_meta"]["id"] and $record["_meta"]["title"]. |
$user |
The viewing user — id, email, name. Always present: an unauthenticated resolve gets a stand-in System user. |
$user["role"] |
Their role in this workspace: admin, readwrite, or readonly. Use it to show different text to admins. The System stand-in reports admin; test $user["system"] if you need to tell it apart. |
$table |
This table's own schema: $table["id"], $table["name"], $table["db_table"], and $table["fields"] keyed by field id. Automations get the same variable. |
$table["fields"] lets a snippet work out at run time what a formula would have to be told. Each entry has id, name, type and options, and a relationship (lookup) field also carries lookup_table (the linked table's id) and lookup_table_name:
$tableId = $table["fields"]["client"]["lookup_table"];
That id can be passed straight to record_get(), records_query() and friends in place of a table name, so a script field that follows a relationship keeps working in a copied workspace where the table names may differ. Prefer lookup_table over lookup_table_name: the name is a snapshot taken when the field was configured and can go stale after a rename.
Never type a table id into your code as a literal. Table ids change when a workspace is copied or installed from the App Market, so a hardcoded id points somewhere else (or nowhere) in the copy. Read the id from $table at run time instead.
You can read the workspace with the same query helpers as a flow — records_query(), records_query_one(), record_get(), records_count() — scoped to this table's workspace.
A script field only displays a value — it must never change data. Do not call record_create(), record_update(), record_delete(), record_email_send(), or record_comment(): the snippet runs every time anyone opens the record, so a side effect there would fire again and again. Anything that writes, sends, or has a side effect belongs in an automation.
Render modes
- Text (default) — the return value is escaped and shown as-is, whitespace preserved. Don't hand-escape; it's automatic.
- HTML — the return value is rendered as HTML in a sandboxed frame. Styles, SVG, and tables are fine; JavaScript does not run. Only return HTML when the field is set to HTML render.
Examples
Derived label (text):
return strtoupper($record["status"]);
Roll up a related total with a read query (text):
$lines = records_query("invoice_lines", ["invoice" => $record["_meta"]["id"]]);
$sum = 0;
foreach ( $lines as $l ) { $sum += floatval($l["amount"]); }
return number_format($sum, 2);
A button linking to something stored on the linked record (HTML render). Nothing is hardcoded, so the same field survives a workspace copy:
$client = record_get($table["fields"]["client"]["lookup_table"], $record["client"]["id"]);
if ( ! $client ) return "";
return "<a href='https://drive.google.com/drive/folders/" . $client["folder"]
. "' target='_blank' style='display:inline-block;padding:4px 10px;border-radius:4px;"
. "background:#1a73e8;color:#fff;text-decoration:none'>Open Drive folder</a>";
A small status badge (HTML render — no JavaScript):
$overdue = $record["due_date"] && strtotime($record["due_date"]) < time();
return $overdue
? "<span style='color:#b00;font-weight:bold'>Overdue</span>"
: "<span style='color:#080'>On track</span>";
Script field vs calculation vs automation
| You want | Use |
|---|---|
| A live derived value from a SQL formula | Calculation field |
| A live derived value that needs code, a query, or HTML | Script field |
| To freeze a value at create/update time | Automation writing a regular field |
| To send mail, call an API, or change another record | Automation |
| To block a bad write before it saves | Validation rule |
Reach for a calculation first — it's cheaper and always current. Move up to a script field when the value genuinely needs logic a formula can't express.