PHPScript basics
PHPScript looks and behaves like PHP. If you know PHP you can skip most of this; if not, this page covers everything you need to write automations.
Variables
No declaration needed. Assign with =. Variable names start with $.
$name = "Acme Inc";
$count = 42;
$active = true;
The trigger record is always available as $record.
Types
Strings, numbers, booleans (true/false), null, and arrays. Values convert as you'd expect in comparisons and string concatenation.
$total = 10 + 5; // 15
$label = "Items: " . $total; // "Items: 15" (. joins strings)
Operators
+ - * / % math
. string concatenation
== != > < >= <= comparison
&& || ! logical and / or / not
Conditionals
if ( $record["status"] == "won" ) {
record_comment("deals", $record["_meta"]["id"], "Closed!");
} elseif ( $record["status"] == "lost" ) {
sys_log("Lost deal");
} else {
sys_log("Still open");
}
Braces are optional when the branch is a single statement, and the opening brace may sit on its own line — both of these behave as in PHP:
if ( $record["status"] == "won" )
sys_log("Closed!");
else
sys_log("Still open");
As in PHP, a braceless branch covers one statement only. If you want more than one thing to happen, use braces.
Loops
$rows = records_query("tasks", ["field" => "done", "op" => "=", "value" => "0"](/help/field-done-op-value-0));
foreach ( $rows as $row ) {
sys_log("Open task: " . $row["title"]);
}
for, while, and do … while are also available, along with break and continue.
Loop bodies follow the same brace rules as conditionals — braces optional for a single
statement, and the opening brace may go on its own line.
Arrays
$list = ["a", "b", "c"];
$list[] = "d"; // append
$person = ["name" => "Sam", "age" => 30]; // associative
sys_log($person["name"]); // Sam
Useful built-ins: count(), implode(), explode(), in_array(), array_keys(), array_values(), array_merge(), array_column(), sort().
JSON
$obj = fromjson('{"a":1,"b":2}'); // parse JSON to an array
$json = tojson($obj); // encode an array to JSON
(json_decode / json_encode work too.)
Strings
Standard PHP string functions are available: trim(), strlen(), substr(), strtolower(), strtoupper(), str_replace(), sprintf(), strpos(), number_format(), and the regex helpers preg_match() / preg_replace(). See also preg_match_gf() for a one-line "match and return a group" helper.
How records are shaped
A record is an associative array. Its fields are keyed by field id, and a special _meta key holds the record's id and title.
$record["name"] // a field value, by field id
$record["_meta"]["id"] // the record's id
$record["_meta"]["title"] // the record's title
In the visual builder, tokens map to this shape:
{{record.name}}→$record["name"]{{record.id}}→$record["_meta"]["id"]
Functions that take a record id (like record_update()) want the numeric id, so pass $record["_meta"]["id"].
Multi-value fields (multi-select, multi-user, multi-lookup) are arrays. To use one inside a string, flatten it with flow_text(); to test membership, use field_contains(). A plain == between an array and a single value is never true, so compare them with flow_eq() instead.
How the table is shaped
Alongside $record you always get $table, describing the table the script belongs to:
$table["id"] // the numeric table id
$table["name"] // the display name
$table["db_table"] // the database table name
$table["fields"] // every field, keyed by field id
Each entry in $table["fields"] has id, name, type and options. A relationship (lookup) field also has lookup_table, the numeric id of the table it points at:
$table["fields"]["client"]["lookup_table"]
Pass that id straight to record_get(), records_query() or any other function that takes a table, and you can follow a relationship without naming the other table. Do not type a table id into your code as a literal: ids change when a workspace is copied, so read them from $table at run time.
Missing values
A field that isn't set — or a key that doesn't exist at all — reads as null. No
warning, no error. So you don't need to guard a lookup before using it:
if ( $record["status"] == "won" ) { // safe even if the record has no status
sys_log("Won!");
}
Writing if ( isset($record["status"]) && $record["status"] == "won" ) adds
nothing. Use empty() when you genuinely mean "is this blank?", not to prove a
key exists:
if ( empty($record["close_date"]) ) {
throw new Exception("Close date is required");
}
Field keys are matched case-insensitively, so $record["Status"] and
$record["status"] are the same field. (array_key_exists() is the exception —
it is case-sensitive, so don't mix it with the above.)
Errors
Throw to stop the flow with an error (recorded on the run log):
if ( ! $record["email"] ) {
throw new Exception("Record has no email");
}
Catch errors you expect:
try {
$resp = curl_get("https://api.example.com/thing");
} catch ( Exception $e ) {
sys_log("Request failed: " . $e->getMessage());
}
Debugging
Use sys_log() liberally — each line shows in the flow's run log, which you can open from the automation's run history.
sys_log("status is " . $record["status"]);
Ready for the functions? Browse the PHPScript function reference.