Node Types
Configuration reference for every node type available in .swirls files.
Nodes are the building blocks of a graph. Each node has a type field that determines its behavior. This page documents all available node types, their fields, and examples.
For how nodes connect to each other, see Graphs. For type-safe access to node outputs, see Context.
code
Run TypeScript in an isolated sandbox.
| Field | Type | Required | Description |
|---|---|---|---|
| code | @ts { } block | Yes | TypeScript to execute. Access context.nodes for upstream data. Return value becomes output. |
| schema | @json { } block | No | Output schema (JSON Schema). Use outputSchema on the root node only. |
node normalize {
type: code
label: "Normalize email"
schema: @json {
{ "type": "object", "required": ["email"], "properties": { "email": { "type": "string" } }, "additionalProperties": false }
}
code: @ts {
const email = context.nodes.root.input.email ?? ""
return { email: email.toLowerCase().trim() }
}
}ai
Invoke an AI model. Supports multiple kinds: text, object, image, video, and embed.
| Field | Type | Required | Description |
|---|---|---|---|
| kind | string | Yes | One of: text, object, image, video, embed. |
| provider | string | No | Which API to call: openrouter (default), anthropic, openai, or google. |
| model | string | Yes | Model identifier (e.g. "anthropic/claude-3.5-sonnet", "openai/dall-e-3"). |
| prompt | @ts { } block | Yes | TypeScript expression returning the prompt string. |
| temperature | number | No | Sampling temperature. |
| maxTokens | number | No | Max output tokens. |
| options | object | No | Kind-specific options (e.g. { n: 1, size: "1024x1024" } for image). |
| schema | @json { } block | No | Output schema. Required for the object kind. |
Example (object kind):
node classify {
type: ai
label: "Classify intent"
kind: object
model: "anthropic/claude-3.5-sonnet"
prompt: @ts {
return `Classify this message: ${context.nodes.root.input.message}`
}
schema: @json {
{
"type": "object",
"required": ["intent", "confidence"],
"properties": {
"intent": { "type": "string" },
"confidence": { "type": "number" }
}
}
}
}Example (image kind):
node generate_image {
type: ai
label: "Generate image"
kind: image
model: "openai/dall-e-3"
prompt: @ts {
return `A professional illustration of: ${context.nodes.root.input.topic}`
}
options: {
n: 1
size: "1024x1024"
}
}switch
Conditional routing. Returns a case name to determine which branch executes.
| Field | Type | Required | Description |
|---|---|---|---|
| cases | string array | Yes | List of case names. |
| router | @ts { } block | Yes | TypeScript expression returning one of the case names. |
Use labeled edges in the flow block: route -["case_name"]-> target.
node route {
type: switch
label: "Route by priority"
cases: ["high", "medium", "low"]
router: @ts {
const score = context.nodes.root.output.score ?? 0
if (score > 80) return "high"
if (score > 40) return "medium"
return "low"
}
}http
Make HTTP requests to external APIs. Use an auth block for OAuth, API key, basic, or bearer auth instead of building Authorization headers by hand: avoid hyphenated header keys like Content-Type in literal headers objects (they break the parser; see Syntax).
| Field | Type | Required | Description |
|---|---|---|---|
| url | @ts { } block or string | Yes | Request URL. |
| method | string | No | HTTP method. Defaults to GET. |
| auth | identifier | No | Name of a top-level auth block. Only http nodes may set auth:. |
| headers | object | No | Request headers. Avoid keys with hyphens. |
| body | @ts { } block | No | Request body. |
| schema | @json { } block | No | Output schema. |
auth api_key_ex {
type: api_key
secrets: api_k
key: API_KEY
header: "X-Api-Key"
}
secret api_k {
vars: [API_KEY]
}
node fetch_data {
type: http
label: "Fetch from API"
method: "POST"
auth: api_key_ex
url: @ts {
return "https://api.example.com/data/" + context.nodes.root.input.id
}
body: @ts {
return JSON.stringify({ query: context.nodes.root.input.query })
}
schema: @json {
{ "type": "object", "properties": { "results": { "type": "array" } } }
}
}Send email via Resend with dynamic content.
| Field | Type | Required | Description |
|---|---|---|---|
| from | @ts { } block or string | Yes | Sender address. |
| to | @ts { } block or string | Yes | Recipient address. |
| subject | @ts { } block or string | Yes | Subject line. |
| text | @ts { } block or string | No | Plain text body. |
| html | @ts { } block or string | No | HTML body. |
| replyTo | @ts { } block or string | No | Reply-to address. |
The output shape is vendor-managed. Setting schema: on an email node will be rejected by the validator.
node send_email {
type: email
label: "Send notification"
from: @ts { return "[email protected]" }
to: @ts { return context.nodes.root.output.email }
subject: @ts { return "Your request has been processed" }
text: @ts {
return `Hello ${context.nodes.root.output.name}, your request is complete.`
}
}stream
Read persisted rows from a top-level stream block in the same file. See Streams for stream { graph:, condition:, prepare: } and context.output in switch graphs.
| Field | Type | Required | Description |
|---|---|---|---|
| stream | identifier | Yes | Name of a top-level stream block. |
| filter | @ts { } block | Yes* | Filter object for rows. Operators: eq, ne, gt, gte, lt, lte, like, in. Return {} for no filter. |
| schema | @json { } block | Recommended | Typically type: "array" of row objects. |
*Required in practice; use return {} when unconstrained.
postgres
Query or write to a user-managed PostgreSQL database declared in a top-level postgres block. Use select: for reads and insert: for writes. Each node references exactly one of select or insert.
| Field | Type | Required | Description |
|---|---|---|---|
| postgres | string | Yes | Name of a top-level postgres block. |
| select | @sql { } block | Yes* | Read-only SELECT (or WITH CTE) query. Mutually exclusive with insert. |
| insert | @sql { } block | Yes* | INSERT statement. Mutually exclusive with select. |
| params | @ts { } block | Yes for insert; optional for select if SQL has {{key}} placeholders | Returns an object whose keys match {{key}} tokens in the SQL. |
| condition | @ts { } block | No | Insert only: if present, insert runs only when this returns true. |
| schema | @json { } block | Recommended for select | JSON Schema for result rows (typically an array of objects). |
*Exactly one of select or insert is required.
Select example:
node load_rows {
type: postgres
label: "Load active rows"
postgres: my_db
select: @sql {
SELECT id, name FROM items WHERE status = {{status}} LIMIT 10
}
params: @ts {
return { status: context.nodes.root.input.status }
}
schema: @json {
{
"type": "array",
"items": {
"type": "object",
"properties": { "id": { "type": "string" }, "name": { "type": "string" } }
}
}
}
}Insert example:
node save_row {
type: postgres
label: "Insert row"
postgres: my_db
insert: @sql {
INSERT INTO items (name, score) VALUES ({{name}}, {{score}})
}
params: @ts {
return {
name: context.nodes.classify.output.name,
score: context.nodes.classify.output.score
}
}
}Declare the database and tables in a top-level postgres block: see Postgres.
graph
Execute another graph as a subgraph.
| Field | Type | Required | Description |
|---|---|---|---|
| graph | string | Yes | Name of the graph to execute. |
| input | @ts { } block | Yes | TypeScript expression mapping input for the subgraph. |
node run_enrichment {
type: graph
label: "Run enrichment"
graph: enrich_contact
input: @ts {
return { email: context.nodes.root.input.email, name: context.nodes.root.input.name }
}
}Subgraph output is available as context.nodes.<graphNodeName>.output.<leafNodeName> where leafNodeName is a leaf node in the child graph.
map
Run a child graph once per element returned from items. Requires maxItems (positive cap). Use either inline subgraph { ... } (no colon after subgraph) or graph: named_graph: exactly one.
Inside the child graph and on the map node’s items block, context.iteration includes index, item (typed from the subgraph root inputSchema), total, and previous. Output is an array of child graph results (one entry per item).
| Field | Type | Required | Description |
|---|---|---|---|
| items | @ts { } block | Yes | Returns an array to iterate. |
| maxItems | number | Yes | Maximum items to process. |
| concurrency | number | No | Reserved for engine chunking. |
| subgraph | block | One of subgraph or graph | Inline child graph with root, optional nodes, and flow. |
| graph | identifier | One of subgraph or graph | Reference to a named graph in the same file. |
node per_ticket {
type: map
label: "Process each ticket"
items: @ts {
return context.nodes.root.output.tickets
}
maxItems: 100
subgraph {
root {
type: code
label: "Normalize"
inputSchema: @json {
{ "type": "object", "required": ["id"], "properties": { "id": { "type": "string" } }, "additionalProperties": false }
}
outputSchema: @json {
{ "type": "object", "required": ["id"], "properties": { "id": { "type": "string" } }, "additionalProperties": false }
}
code: @ts {
const item = context.iteration.item
return { id: item.id.trim() }
}
}
}
}while
Run a child graph repeatedly until condition is false or maxIterations is reached. Requires input, condition, update, and maxIterations. Same subgraph { } vs graph: choice as map.
context.iteration includes index, input (threaded state from root inputSchema), and previous (last iteration’s leaf outputs). Output shape is { iterations: number; lastOutput: ... }.
| Field | Type | Required | Description |
|---|---|---|---|
| input | @ts { } block | Yes | Initial state for the loop. |
| condition | @ts { } block | Yes | If true, another iteration runs (after update). |
| update | @ts { } block | Yes | Produces the next input for the child graph. |
| maxIterations | number | Yes | Hard cap on iterations. |
| subgraph | block | One of subgraph or graph | Inline child graph. |
| graph | identifier | One of subgraph or graph | Named graph in the same file. |
node refine_loop {
type: while
label: "Refine draft"
input: @ts {
return { draft: context.nodes.merge.output.text }
}
condition: @ts {
return context.iteration.index < 2
}
update: @ts {
return { draft: context.iteration.previous?.polish?.text ?? context.iteration.input.draft }
}
maxIterations: 5
subgraph {
root {
type: code
label: "Polish"
inputSchema: @json {
{ "type": "object", "required": ["draft"], "properties": { "draft": { "type": "string" } }, "additionalProperties": false }
}
outputSchema: @json {
{ "type": "object", "required": ["text"], "properties": { "text": { "type": "string" } }, "additionalProperties": false }
}
code: @ts {
return { text: context.iteration.input.draft + " (refined)" }
}
}
}
}Downstream code can read context.nodes.refine_loop.output.lastOutput.<leafName>.
scrape
Fetch and extract content from web pages via Firecrawl.
| Field | Type | Required | Description |
|---|---|---|---|
| url | @ts { } block | Yes | URL to scrape. |
| onlyMainContent | boolean | No | Strip navigation and boilerplate when true. |
| formats | array of strings | No | Response formats (e.g. markdown). |
| maxAge | number | No | Cache max age hint. |
| parsers | array of strings | No | Parser hints for the API. |
The output shape is vendor-managed. Setting schema: on a scrape node will be rejected by the validator.
parallel
Web search and URL extraction via Parallel.ai. See the Parallel guide.
| Field | Type | Required | Description |
|---|---|---|---|
| operation | search | extract | findall | Yes | API mode. |
| objective | @ts { } block | Yes | Return a string describing the goal. |
| searchQueries | @ts { } block | Search | Return string[] of keyword queries. |
| urls | @ts { } block | Extract | Return string[] of URLs. |
| mode | string | No | Search: one-shot, agentic, or fast (default fast). |
| excerptsMaxCharsPerResult | number | No | Search: excerpt size hint per result. |
| excerptsMaxCharsTotal | number | No | Search: total excerpt budget. |
| excerpts | boolean | No | Extract: include excerpts. |
| fullContent | boolean | No | Extract: include full content. |
| entityType | @ts { } block | FindAll | Entity type label. |
| matchConditions | @ts { } block | FindAll | Match rules array. |
| matchLimit | number | No | FindAll: max matches. |
| pollInterval | number | No | FindAll: polling interval. |
| pollTimeout | number | No | FindAll: max wait before timeout. |
See Parallel for full FindAll fields.
wait
Pause execution for a duration.
| Field | Type | Required | Description |
|---|---|---|---|
| amount | number | No | Numeric delay amount. |
| unit | string | No | One of: seconds, minutes, hours, days. Use with amount. |
bucket
S3-like storage operations.
| Field | Type | Required | Description |
|---|---|---|---|
| operation | string | Yes | One of: upload, download, delete. |
| path | @ts { } block or string | No | Object path. |
| bucket | string | No | Bucket name. |
| schema | @json { } block | No | Output schema. |