Postgres
Declare PostgreSQL connections and table schemas for use in workflow nodes.
What it is. The PostgreSQL database you already have, declared with its table schemas so workflows can use it.
Use it when workflow steps should read or write your existing records with parameterized SQL.
Works with type: postgres nodes (the SQL steps) and secret blocks (the connection credentials). For Swirls-managed output storage, use a stream instead. For a new relational database you want Swirls to provision and migrate, use Database instead.
postgres blocks represent user-managed external PostgreSQL databases: databases you operate, outside Swirls platform-managed streams and type: stream nodes, and outside the Swirls-managed Database primitive. After you declare a connection and table row schemas, type: postgres nodes can run parameterized SELECT (select:), INSERT (insert:), UPDATE (update:), or DELETE (delete:) SQL against those tables.
Declaring a postgres block
There is no type: field: the keyword postgres identifies the block.
| Field | Required | Description |
|---|---|---|
label | No | Human-readable label. |
secrets | No | References a top-level secret block; bare connection identifiers are validated against that block’s vars. |
connection | Yes | Connection string: a bare identifier (secret ref) or a quoted literal for testing (the validator warns on literals in production-oriented workflows). |
table <name> { } | Yes (≥1) | Each nested block declares a table name and a JSON Schema for one row. |
postgres my_db {
label: "CRM database"
secrets: project_secrets
connection: SHARED_DATABASE_URL
table leads {
schema: @json {
{
"type": "object",
"properties": {
"id": { "type": "string" },
"email": { "type": "string" },
"score": { "type": "number" }
},
"required": ["id", "email"]
}
}
}
}Table names in the DSL are unqualified (for example leads); runtime targets your database's public schema.
Three connection modes work:
- Secret-block reference:
secrets: my_secretsplusconnection: DATABASE_URL. The identifier is validated against the block'svars. - Project secret:
connection: DATABASE_URLwith nosecrets:. The bare identifier resolves as a project secret. - Literal:
connection: "postgresql://...". The validator warns; use it for testing only.
Declared table schemas drive validation and editor tooling only. They are never enforced against the live database, so a drifted schema surfaces as a query-time error, not a deploy-time one. Postgres nodes run in deployed projects on Swirls Cloud.
Table declarations and tooling
Each table block’s schema is JSON Schema for a single row. The language service uses it to:
- Validate that explicit
INSERT INTO t (col1, col2)column names exist on the declared table. - Infer TypeScript types for
paramsreturn objects and for{{key}}hovers from SQL (for example, which column a placeholder maps to).
Select nodes (select:)
Use exactly one of select:, insert:, update:, or delete: on a type: postgres node. These are the only four statement kinds: there is no arbitrary-statement node. For reads, use select: with a SELECT (or WITH) statement.
| Field | Required | Description |
|---|---|---|
postgres | Yes | Name of a top-level postgres block. |
select | Yes | @sql block: must read as a SELECT (or WITH) statement. |
params | Optional; required when the SQL contains {{key}} placeholders | @ts block returning a plain object whose keys match those placeholders. |
schema / outputSchema | Recommended | JSON Schema for the array of result rows. |
node load_leads {
type: postgres
label: "Active leads"
postgres: my_db
select: @sql {
SELECT id, email, score
FROM leads
WHERE score >= {{min_score}}
}
params: @ts {
return {
min_score: context.nodes.root.output.threshold
}
}
schema: @json {
{
"type": "array",
"items": {
"type": "object",
"properties": {
"id": { "type": "string" },
"email": { "type": "string" },
"score": { "type": "number" }
}
}
}
}
}Node output is an array of row objects (query result rows).
Insert nodes (insert:)
For writes, use insert: with an INSERT statement. params: is required.
| Field | Required | Description |
|---|---|---|
postgres | Yes | Name of a top-level postgres block. |
insert | Yes | @sql block: an INSERT statement. Upsert via ON CONFLICT is allowed. |
params | Yes | @ts block returning a plain object whose keys match {{key}} in the SQL. |
condition | No | @ts block returning a boolean; if false, the insert is skipped. |
Wrap row values in VALUES (...): the validator requires VALUES to be followed by parentheses around the value list.
node upsert_lead {
type: postgres
postgres: my_db
condition: @ts {
return context.nodes.classify.output.score > 20
}
insert: @sql {
INSERT INTO leads (name, email, score)
VALUES ({{name}}, {{email}}, {{score}})
}
params: @ts {
return {
name: context.nodes.root.output.name,
email: context.nodes.root.output.email,
score: context.nodes.classify.output.score
}
}
}Typical output shape is { executed: true } or, when condition is false, { skipped: true }.
Update nodes (update:)
To modify existing rows, use update: with an UPDATE statement. params: is required when the SQL contains {{key}} placeholders; a static statement may omit it.
| Field | Required | Description |
|---|---|---|
postgres | Yes | Name of a top-level postgres block. |
update | Yes | @sql block: an UPDATE statement. |
params | When the SQL has {{key}} placeholders | @ts block returning a plain object whose keys match those placeholders. |
condition | No | @ts block returning a boolean; if false, the update is skipped. |
node refresh_score {
type: postgres
postgres: my_db
update: @sql {
UPDATE leads
SET score = {{score}}
WHERE email = {{email}}
}
params: @ts {
return {
score: context.nodes.classify.output.score,
email: context.nodes.root.output.email
}
}
}An UPDATE without a WHERE clause modifies every row in the table, so the validator warns on it. Explicit SET col = ... column names are validated against the declared table schema, the same way explicit INSERT column lists are.
Delete nodes (delete:)
To remove rows, use delete: with a DELETE statement. The same params: and condition: rules as update: apply.
| Field | Required | Description |
|---|---|---|
postgres | Yes | Name of a top-level postgres block. |
delete | Yes | @sql block: a DELETE statement. |
params | When the SQL has {{key}} placeholders | @ts block returning a plain object whose keys match those placeholders. |
condition | No | @ts block returning a boolean; if false, the delete is skipped. |
node purge_stale_leads {
type: postgres
postgres: my_db
condition: @ts {
return context.nodes.root.output.confirmed === true
}
delete: @sql {
DELETE FROM leads
WHERE score < {{min_score}}
}
params: @ts {
return { min_score: context.nodes.root.output.threshold }
}
}A DELETE without a WHERE clause removes every row in the table, so the validator warns on it.
Like insert nodes, update and delete nodes produce { executed: true } or { skipped: true }; they return no rows.
Parameter interpolation ({{key}})
Values are not concatenated into the SQL string. At runtime, each {{key}} is replaced with a positional placeholder ($1, $2, …) and the corresponding value is sent as a bound parameter: so user data never becomes raw SQL text.
In the DSL, params: returns a named object. Placeholder names in SQL and keys in that object must match when placeholders exist. Those names do not need to match JSON Schema property names on the row: types are inferred from SQL position (for example, which column appears in INSERT INTO t (a,b) VALUES ({{x}},{{y}})) or from simple comparison patterns (for example WHERE col = {{key}} on SELECT, UPDATE, or DELETE, and SET col = {{key}} on UPDATE).
Type safety in the editor
When table row schemas are declared and SQL is parseable by the tooling, you get:
- Hover types on
{{key}}inside@sqlblocks. - Type checking on the object returned from
params@tsblocks. - An index signature on the inferred params type so extra keys remain valid when you map names freely.
The Node types reference lists all postgres node fields in one place.
Database
A Swirls-managed PostgreSQL database, declared with a Prisma schema, migrated automatically, and queried from workflows through a generated typed client.
Connections
How your system talks to the outside world safely. Secrets hold values, auth blocks describe credential profiles, and connections broker OAuth with no keys at all.