SWIRLS_
Language

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's inputSchema. Use context.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's outputSchema). Downstream nodes read normalized or derived fields here.
  • context.nodes.<nodeName>.output: Output of an upstream node, typed from that node's outputSchema. 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] on root { } or node name { } (identifiers or strings). Only those names are injected from the project vault (plus optional inferred platform keys below).
  • Inferred without listing: OPENROUTER_API_KEY for ai, RESEND_API_KEY for email, FIRECRAWL_API_KEY for scrape. Locally, set these in .swirls env (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:

  1. Reads JSON Schemas from inputSchema, outputSchema, and review schema fields.
  2. Converts them to TypeScript interfaces.
  3. Declares context with the correct types.
  4. 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
  }
}

Further reading

  • Graphs: graph structure and node definitions
  • Reviews: accessing review data via context.reviews

On this page