Automation triggers, nodes, and expressions
Every automation trigger and node kind, the values each needs and produces, expression paths, templates, operations, predicates, and graph outcomes.
An Automation is a versioned graph. A trigger creates a durable run, nodes read the trigger or earlier outputs, and labelled edges decide what runs next. Published versions are immutable; editing creates draft state until you publish again. Each run keeps its source, trigger payload, node attempts, resolved input, output, and error.
Canonical contract lists
Canonical contract values:
manualscheduleeventwebhook
Canonical contract values:
actionconditiontransformdelayfor_eachapprovalconnector_operationprovider_operationconnector_actionai_promptwebsub_automationmergedatabase_recorddatabasenotify
Canonical contract values:
coalesceconcataddsubtractmultiplydividelowercaseuppercaselengthformat_payloadpayload_titleformat_dateadd_monthsextract_textfilter_list
Canonical contract values:
equalsnot_equalscontainsnot_containsgreater_thangreater_than_or_equalless_thanless_than_or_equalis_emptyis_not_emptyexistsnot_exists
Trigger kinds
Manual trigger manual
Starts only when a person or caller explicitly runs or tests the Automation.
It has no trigger-specific settings. The caller may supply a trigger payload;
paths under trigger read that payload. A test run is recorded with source
test, while an ordinary invocation uses manual.
Schedule trigger schedule
Starts at an interval or cron schedule. An interval requires
intervalMinutes. Cron requires both cronExpression and an IANA timezone.
The scheduler validates the schedule and records the next run time. Scheduled
runs do not acquire an event-catalog payload merely because they run on a
clock.
Event trigger event
Listens to one or more catalog definitions. Each saved event contains a
definitionId, required resource scope, and optional catalog refinements.
The trigger can also carry predicates over the produced event payload. The old
single eventType and subjectType fields still decode, but the editor writes
the multi-event shape.
Scope is part of subscription matching, not a condition evaluated after every event. An event definition with required scope must save those ResourceRefs, including their human labels. The live definitions and produced paths are in Automation event catalog.
Webhook trigger webhook
Starts when an external caller posts to the Automation’s generated webhook path. Publishing owns the secret, and publishing can rotate it. The parsed request becomes the trigger payload. The trigger config itself has no URL, method, or body schema fields; validate expected data with early Conditions. See Automate with webhooks.
Node kinds
Every node has a stable ID, optional label, canvas position, and optional retry policy: maximum attempts, initial delay, multiplier, and maximum delay. A node may only reference nodes that are guaranteed to have run before it.
Action node action
Requires a saved actionId. Its optional input map resolves expressions and is
passed as runtime Action input, along with the typed trigger when available.
The node produces the Action run’s result. Because the exact shape belongs to
the selected Action, a generic Action node has no static output shape of its
own.
Condition node condition
Requires one predicate: left expression, operator, and an optional right
expression. It produces { result: boolean } and takes the true or false
edge accordingly. Empty and existence operators do not need a right side.
Transform node transform
Requires a map from output keys to expressions. It evaluates every entry and produces that map unchanged in shape. This is the main way to rename paths, combine text, calculate numbers, or reduce a large earlier result to the values later steps need.
Delay node delay
Requires durationSeconds. The run parks durably until its ready time instead
of holding a process open. On resumption it produces { resumedAt } as an ISO
date. A negative or nonsensical duration is not described as useful behaviour;
authors should use a non-negative duration.
For-each node for_each
Its items is either an expression that must resolve to an array, or a
database_records source with a fixed or expression-derived database ID and
optional limit. It produces { items, count }; each iteration exposes its
current value and index through scope, and item/done edges separate the
loop body from continuation.
The runtime accepts at most 2,000 items per run. A direct database source pages
up to that limit; a searched Database record node is separately capped at 100.
maxConcurrency controls how many iterations may progress together.
Approval node approval
Pauses the run with optional human instructions. Resuming requires an
approved or rejected decision and may include additional output. The
declared downstream value is { decision }. This is a durable wait, not a
Condition that guesses consent.
Connector operation node connector_operation
Requires a legacy connector and operation. settings and runtime input are
expression maps. At execution Snoze chooses the workspace’s oldest installed
connection for that connector; a missing connection is a non-retryable
configuration error. The raw connector result becomes the output, and a
catalog shape is used when one is declared.
Provider operation node provider_operation
Requires an exact connectionId and operation, with optional resource ID,
expression input, and confirmation flag. Picked ResourceRefs are unwrapped to
provider IDs at execution. The provider result becomes the output, whose named
tokens come from the operation catalog. Provider permissions, mutation flags,
and confirmation requirements remain in force.
App action node connector_action
Runs one action of a connected app live, such as linear.create_issue,
slack.post_message or stripe.list_invoices. input is an expression map;
each value is converted to the type the action’s field expects, and the
action’s own output fields are what later nodes can read. The account is the
automation’s Run as connection when it belongs to that app, otherwise the
step’s chosen workspace connection, otherwise the app’s only workspace
connection; a person’s own account (their email) is used only when the
automation runs as that person. Actions that cannot be taken back, like
posting a message or sending an email, run only after a person turns on
“Allow this action to make changes” for the step. A failure the app calls
temporary is retried only for reads and actions that are safe to repeat.
Web access node web
Search public web sources, read a webpage, or research named output fields. Search supports domain and publication-date filters; search and page reads can require a maximum cache age. Retrieval time is not the date of a quote: verify the date and units in the source before using time-sensitive values.
Structured research returns typed values with citations for each field and resumes its saved research run while waiting. It supports domain filters, but strict cache-age and publication-date guarantees require search/read steps. Missing, incomplete or insufficient evidence fails the step; use a failure route to handle unavailable sources. Research objectives, field descriptions and source controls share a 25,000-character request budget.
Parallel must be configured for extraction guarantees and structured research. Basic search remains available through configured legacy providers when no source or freshness controls are requested.
AI prompt node ai_prompt
Requires a prompt expression that resolves to non-empty content. Structured
values are rendered rather than converted to [object Object]. Choose a model
or use Auto, and optionally show the transcript in the workspace chat list.
Access has three choices: Read only, Edit selected items, and Full access. Selected folders include their current descendants; selected databases include their records. Selected-item access allows ordinary reads, while restricting changes to those items and excluding workspace settings and connected-app changes. Every choice still respects the automation’s existing permissions. Older steps retain their saved tool restrictions until an access option replaces them.
Attachments accepts a workspace file ID, a file:// reference, or a list
of those values from an earlier step or a database file field. The step uses
the same file reading support as AI chat. For Gmail triggers, choose Email →
Attachments to pass the files directly: Snoze downloads them through the
connected account and stages private AI files when the step runs. They follow
chat-file access until explicitly saved to the workspace. Attachment names and
counts describe the files but cannot be used to read their contents. A step can
read up to 10 files, with a 25 MB limit per email attachment.
Allow user input is off by default. The AI knows it is running an automation, uses the available context, and stops with a blocker if essential input is missing. When enabled, it can ask an essential question or request approval; the run shows Waiting for user input and resumes after a response. Downstream steps wait with it.
It produces text, toolCalls, blocked, and chatId. A blocked turn does
not report success or let successors run.
Sub-automation node sub_automation
Requires another automationId and may map expressions into its trigger input.
The parent waits for the child run and exposes the child Automation’s result.
That shape is dynamic because it belongs to the selected child. Use this for a
reused workflow, while avoiding recursive call chains.
Merge node merge
Joins graph paths with optional strategy all or any. all waits for all
required incoming paths; any can continue when an eligible path arrives. It
produces { merged: true }. It combines control flow, not the object outputs
from those branches; use a Transform to construct a combined value.
Database record node database_record
Performs create, update, delete, find, or list in a fixed database or
one produced by an earlier node. Create accepts title and field expression
maps and returns record ID, title, and data. Update additionally requires a
record ID and returns the post-update record. Delete requires a record ID and
returns record ID plus deleted: true. Find accepts optional text search and
returns found and a record or null. List accepts search and limit and returns
records and count.
Unsearched List pages up to a clamped 1–2,000 limit, default 50. Searched List uses the search path and returns at most 100. Writes still enforce writable field kinds, validation, and database permissions.
Database node database
Performs duplicate or instantiate_template. Duplicate needs a fixed or
expression-derived source database ID, optional target label, and
includeRecords (default true). Instantiate template needs templateId and an
optional target label. Both produce { databaseId, label }, which can feed a
Database record or For-each node later in the same run.
Send to Inbox node notify
Sends a Snoze notification to workspace members’ Inbox. recipients resolves
to user IDs or emails — fixed people, a person field such as a record’s
assignee, or a list from an earlier step — and is capped at 50 people per run.
title is required and body is an optional message shown under it. link is
optional: a record ID, a page or other item ID, or an https:// address that
opening the notification shows. A recipient who cannot open the linked item
still receives the message, without the link. Recipients who are not members
of the workspace are skipped, not an error. Each person’s notification
preferences decide whether it also arrives by push or email (push is on and
email off by default). It produces { recipients, count, skipped }. A retried
step never notifies anyone twice.
Value expressions
A literal expression stores a fixed JSON value. A path expression has
source: trigger | node | scope and a string path; node paths also require
nodeId. Array indices are decimal path components. A missing segment resolves
to undefined.
A template interpolates {{ trigger.path }}, {{ scope.path }}, or
{{ nodes.nodeId.path }}. Missing values become an empty string. It is string
interpolation, not JavaScript. An operation evaluates its argument expressions
first and applies one of the following operators.
Use the Format value button beside an expression to choose a formatting operation and edit its arguments. Before and After use the values you entered or the selected run sample; an unavailable source stays unresolved. Apply saves the expression, while Cancel leaves the original value unchanged. Formatting previews do not execute steps or write records. You can edit an existing formula, nest formulas, or remove its outer formatting to restore the first argument.
First non-empty coalesce
Returns the first argument that is not undefined, null, an empty string, or an empty array. Other objects and zero are not empty.
Join text concat
Converts every argument to text, treats null or undefined as an empty string, and joins without an automatic separator.
Add numbers add
Converts finite numeric values and adds them. A non-numeric argument contributes zero.
Subtract numbers subtract
Starts with the first finite numeric argument, or zero, then subtracts each remaining finite value. A non-numeric remaining argument subtracts zero.
Multiply numbers multiply
Multiplies finite numeric arguments, starting from one. A non-numeric argument is treated as zero and therefore makes the result zero.
Divide numbers divide
Starts with the first finite number, or zero, and divides by each remaining
number. Division by zero or by a non-numeric value produces NaN; do not rely
on that as a portable downstream value.
Lowercase text lowercase
Converts the first argument to text and applies Unicode-aware JavaScript lowercasing. A null or missing value becomes an empty string.
Uppercase text uppercase
Converts the first argument to text and uppercases it. A null or missing value becomes an empty string.
Value length length
Returns string length, array length, or the number of keys on an object. Other values return zero.
Format payload format_payload
Formats a trigger or step payload into readable text. Its optional second argument controls the payload presentation settings. This is the product’s bounded payload formatter, not arbitrary serialisation.
Payload title payload_title
Derives the short display title Snoze uses for a payload. Use it for labels or notifications when the full formatted payload would be too large.
Format date format_date
Renders an instant (an ISO string such as a schedule trigger’s scheduledFor,
or a date field value) as text in a time zone. Arguments: the value, a pattern,
and an optional IANA time zone (UTC when omitted). Patterns: month_year
(“September 2026”), year_month (“2026-09”), month, year, date (“September
1, 2026”), iso_date (“2026-09-01”, the default). A date without a time of day
(a date field’s “2026-09-01”) is that day in every zone. A recurring automation
that names a period should derive the name this way rather than typing a month.
Add months add_months
Shifts an instant by whole months in a time zone and returns the ISO instant the
same day of the target month (clamped to that month’s length) begins in that
zone. A date without a time of day moves to a date: “2026-01-31” plus one month
is “2026-02-28” in every zone. Arguments: the value, the number of months
(negative for earlier), and an optional IANA time zone. Combine with format_date to label the period that just ended:
format_date(add_months(scheduledFor, -1), "month_year", zone).
Extract text extract_text
Arguments: source text, text to start after, and text to stop before. Delimiters are literal, case-sensitive text, not regular expressions. Extraction starts after the first opening delimiter and ends at the first closing delimiter after it. Leave the opening delimiter empty to start at the beginning, or the closing delimiter empty to continue to the end.
For example, extract_text("Order [AB-42] accepted", "[", "]") returns
"AB-42". An absent delimiter returns empty text, which can feed coalesce
for a fallback. A source that is not text returns no value. The operation does
not extract every occurrence or parse arbitrary HTML.
Filter a list filter_list
Arguments: a list, an item field path, a rule, and an optional comparison value.
Leave the path empty to compare each whole item, or use a dotted path such as
order.amount to read a field within each item. Only the item’s own properties
are read. Matching items retain their original values and order; the source
list is unchanged.
Rules are equals, not_equals, contains, greater_than, less_than,
exists, and is_empty. Equality preserves types: numeric 1 does not equal
text "1"; objects and arrays compare by identity, not deep structure.
Contains uses case-sensitive text matching or array membership. Greater-than
and less-than compare finite numeric conversions; missing or empty values do
not qualify. In this list filter, Have a value (exists) excludes null,
undefined, empty text, and empty lists, but retains false and zero. Are
empty (is_empty) selects those empty values. These two rules do not need a
comparison value.
For example, filter_list([{ "amount": 0 }, { "amount": 12 }], "amount", "greater_than", 5) returns [{ "amount": 12 }]. A non-list source returns no
value. For literal comparisons, choose Text, Number, or True / false beside
Compare with; run-data values retain their source type. A missing sample
does not produce a fabricated filtered result in the preview.
Predicate operators
Exactly equal equals
Uses identity equality. Objects and arrays compare by identity rather than by deep structure.
Not exactly equal not_equals
Negates identity equality.
Contains contains
For text, tests a case-sensitive substring after converting the right value to text. For arrays, tests whether an element is identity-equal to the right value. Other left values return false.
Does not contain not_contains
Negates contains, so unsupported left values return true.
Greater than greater_than
Converts both sides to finite numbers and compares them. A failed conversion does not behave like ordinary text comparison and evaluates false.
Greater than or equal greater_than_or_equal
The inclusive numeric comparison, with the same finite-number conversion.
Less than less_than
The strict numeric comparison, with the same finite-number conversion.
Less than or equal less_than_or_equal
The inclusive numeric comparison, with the same finite-number conversion.
Is empty is_empty
True for undefined, null, an empty string, or an empty array. It does not treat an empty object, false, or zero as empty.
Is not empty is_not_empty
Negates the Automation definition of empty.
Exists exists
True when the left side is neither undefined nor null. An empty string exists.
Does not exist not_exists
True only for undefined or null.
Edges and outcomes
Edges may run always, on success or failure, on Condition true or
false, or for For-each item and done. Failure edges are explicit recovery
paths; a graph can also name a separate failure Automation. The compiler
rejects invalid references and graph arrangements before publication, but a
valid graph can still fail at run time when live data, a connection, or an
external provider is unavailable.