Syntax
Comments, identifiers, values, and embedded @ts / @json / @sql blocks.
This page covers the foundational syntax of the Swirls DSL: how to write comments, name resources, express values, and embed TypeScript, JSON, and SQL with @ts { }, @json { }, and @sql { }.
Comments
Single-line comments start with //.
// This is a comment
form contact {
label: "Contact" // inline comment
}Multi-line comments use /* ... */.
/*
This is a
multi-line comment
*/
form contact {
label: "Contact"
}A /* ... */ comment placed immediately before a declaration becomes a doc comment. The LSP shows it on hover.
/* Collects a visitor's email address for lead capture. */
form contact {
label: "Contact"
enabled: true
}Identifiers
Identifiers name resources (top-level blocks), graphs, and nodes within a graph. They match the pattern [a-zA-Z_][a-zA-Z0-9_]*.
form contact_form { ... }
graph process_contact { ... }Names must be unique within their kind. Two forms cannot share the same name, but a form and a graph can.
Values
Strings
Strings use double quotes.
label: "Process Contact"Numbers
Numbers are integers or floats.
temperature: 0.7
maxTokens: 512Booleans
enabled: true
stream: falseObjects
Objects use { key: value, ... } syntax. Prefer keys without hyphens: hyphenated keys in object literals (for example header names) are parsed incorrectly and can silently break the rest of the file. For HTTP auth, use a top-level auth block and auth: on http nodes instead of raw Content-Type / Authorization headers.
options: { n: 1, size: "1024x1024" }Arrays
tags: ["ai", "lead-gen", "email"]Embedded code (@ts, @json, @sql)
Use @ts { ... }, @json { ... }, or @sql { ... } to embed code in field values. Braces are balanced: inner { / } in TypeScript or JSON are allowed. The closing } ends the block.
TypeScript (@ts)
TypeScript blocks hold executable expressions for node code fields, LLM prompt fields, email fields, and top-level stream block condition / prepare fields. The context object is in scope and typed by the LSP based on the schemas in the same file.
code: @ts {
const email = context.nodes.root.input.email ?? ""
return { email: email.toLowerCase() }
}You can move the same body into a standalone file with extension .ts.swirls and reference it from the graph:
code: @ts "./handlers/entry.ts.swirls"Paths are resolved relative to the directory of the .swirls file that contains the reference. The file must use the .ts.swirls suffix. Semantics match an inline @ts { } block: the file contents are the body of an expression function, so you typically use return ….
In the editor, standalone .ts.swirls files use the Swirls language server with the same context typing as inline blocks, as long as the file is referenced from a parent .swirls file.
JSON (@json)
JSON blocks hold structured content, primarily JSON Schema objects for schema, inputSchema, and outputSchema fields.
schema: @json {
{
"type": "object",
"required": ["email"],
"properties": { "email": { "type": "string" } },
"additionalProperties": false
}
}SQL (@sql)
SQL blocks hold queries for type: postgres nodes (select: / insert:). Use {{key}} placeholders in the SQL with matching keys in the node's params: block: see Postgres.
select: @sql {
SELECT id, email, created_at
FROM users
WHERE email = {{email}}
}Top-level structure
Optionally declare version: once at the top. The rest of the file may contain any number of these top-level blocks in any order:
| Block | Purpose |
|---|---|
schema | Reusable JSON Schema (schema name { … }) |
form | Form resource |
webhook | Webhook resource |
schedule | Cron schedule resource |
secret | Named group of secret variable identifiers |
auth | HTTP authentication profile (for http nodes) |
postgres | External PostgreSQL connection and table schemas |
stream | Persist graph output to a typed stream |
graph | Workflow DAG |
trigger | Bind a resource to a graph |
version: 1
schema contact_payload { ... }
secret creds { ... }
form contact { ... }
graph process_contact { ... }
trigger on_contact { ... }Block order does not matter. The compiler resolves references by name. Names must be unique within their kind across the entire file.
Named schemas
Declare a schema once and reference it anywhere a schema is accepted (form / webhook / stream, root inputSchema / outputSchema, node outputSchema / schema, review schema):
schema contact_payload {
label: "Contact payload"
schema: @json {
{
"type": "object",
"required": ["email"],
"properties": { "email": { "type": "string" } },
"additionalProperties": false
}
}
}
form contact {
label: "Contact"
schema: contact_payload
}Inline object-literal schemas
Besides @json { … }, you can write JSON Schema as an inline object with unquoted keys (DSL object syntax). It produces the same AST as @json.
root {
type: code
label: "Entry"
inputSchema: {
type: "object"
required: ["title", "body"]
properties: {
title: { type: "string" }
body: { type: "string" }
}
additionalProperties: false
}
code: @ts { return context.nodes.root.input }
}