Context and Type Safety
How the context object works in TypeScript blocks and how the LSP provides type safety.
Every ts block in a .swirls file has a typed context object in scope. The Swirls LSP generates TypeScript types from your schemas so you get autocomplete and type checking without leaving the .swirls file.
context.nodes
context.nodes.root.input: The graph trigger payload (form, webhook, schedule, etc.), typed from the root node'sinputSchema. Usecontext.nodes.root.input.email,context.nodes.root.input.name, etc.context.nodes.root.output: After the root node runs, its return value (typed from the root node'soutputSchema). Downstream nodes read normalized or derived fields here.context.nodes.<nodeName>.output: Output of an upstream node, typed from that node'soutputSchema. For example:context.nodes.entry.output.email.
Downstream nodes do not define inputSchema in the DSL. Their input is derived from upstream output schemas automatically.
context.reviews
Available when upstream nodes have review enabled. Keyed by node name.
context.reviews.<nodeName>: The review form output from a reviewed node.- The current node can also read its own review data:
context.reviews.<currentNodeName>. - When a review node defines a schema, the LSP types the review data from that schema.
See Reviews for details.
context.secrets
Project secrets available to this node’s TypeScript blocks.
- Declare which keys to expose with
secrets: [KEY1, KEY2]onroot { }ornode name { }(identifiers or strings). Only those names are injected from the project vault (plus optional inferred platform keys below). - Inferred without listing:
OPENROUTER_API_KEYforai,RESEND_API_KEYforemail,FIRECRAWL_API_KEYforscrape. Locally, set these in.swirlsenv (swirls env set OPENROUTER_API_KEY=...). In cloud, the project vault is merged first; operator env fills the same names when missing—billing only counts operator env when that merge path supplied the winning credential. - For ai, email, and scrape, list a custom vault key in
secrets: [...]first if you want BYOK under another name. Runtime walks those names in order, then falls back to the inferred default key.
The LSP types context.secrets as an object with optional properties for each allowed key (same rules as runtime).
context.meta
Execution metadata:
context.meta.triggerId: ID of the trigger that started this execution.context.meta.triggerType: Type of trigger (form,webhook,schedule,agent).
How the LSP builds types
The LSP creates a virtual TypeScript document for each ts block:
- Reads JSON Schemas from
inputSchema,outputSchema, and reviewschemafields. - Converts them to TypeScript interfaces.
- Declares
contextwith the correct types. - The TypeScript language service provides completions, hover info, and error checking.
You don't write type annotations. The LSP derives them from your schemas.
Example
This graph shows how context typing flows through nodes. The root node normalizes input from context.nodes.root.input, and the downstream greet node reads context.nodes.root.output with full type safety.
graph typed_example {
label: "Type Safety Demo"
root {
type: code
label: "Entry"
inputSchema: @json {
{
"type": "object",
"required": ["name", "email"],
"properties": {
"name": { "type": "string" },
"email": { "type": "string" }
},
"additionalProperties": false
}
}
outputSchema: @json {
{
"type": "object",
"required": ["name", "email"],
"properties": {
"name": { "type": "string" },
"email": { "type": "string" }
},
"additionalProperties": false
}
}
code: @ts {
// context.nodes.root.input is typed from inputSchema
const { name, email } = context.nodes.root.input
return { name: name.trim(), email: email.toLowerCase() }
}
}
node greet {
type: code
label: "Generate greeting"
outputSchema: @json {
{
"type": "object",
"required": ["greeting"],
"properties": { "greeting": { "type": "string" } },
"additionalProperties": false
}
}
code: @ts {
// context.nodes.root.output is typed from the root outputSchema
const name = context.nodes.root.output.name
return { greeting: `Hello, ${name}!` }
}
}
flow {
root -> greet
}
}