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 (form, webhook, schedule, graph, trigger) 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.
headers: { "Content-Type": "application/json", "X-Api-Key": "secret" }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 persistence conditions. 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 stream nodes. Use {{table}} as a placeholder for the stream table name that Swirls resolves at runtime.
query: @sql {
SELECT email, created_at
FROM {{table}}
WHERE created_at > NOW() - INTERVAL '7 days'
}Top-level structure
A .swirls file can optionally declare a version at the top. The rest of the file contains any number of form, webhook, schedule, graph, and trigger blocks in any order.
version: 1
form contact { ... }
graph process_contact { ... }
trigger on_contact { ... }Block order does not matter. The Swirls compiler resolves references by name. Names must be unique within their kind across the entire file.