Appearance
SQL node
IMPORTANT
This node is an installable component using the sql host capability. It never sees your connection string: it describes the query, and the engine — which owns the driver and does the parameter binding — runs it.
The statement resolves ${secrets.X} only, never flow refs. Request data belongs in the parameters, where it is bound. That split is enforced, not advised.
The SQL node reads or writes a Postgres database mid-flow — the relational counterpart to the MongoDB node. Look up a customer, insert an order, update a status, run a report.
Component id: flowrunner/sql.
How it works
The SQL node connects to Postgres, runs one query, and stores the result. You provide:
- a connection string, as a secret reference (
${secrets.DATABASE_URL}) so the credentials never live in the flow; - a query with numbered placeholders —
$1,$2, … — for any dynamic value; - a list of parameters, one per line: each is a flow reference (
start.customerId) whose value is bound, or a literal.
The parameters are passed to Postgres as bound values, never spliced into the SQL text — so a value like start.email can't inject SQL, no matter what a form visitor typed. The query itself only resolves ${env.X} / ${secrets.X}; flow references are never interpolated into it.
The result is stored under your result variable:
text
refs.<resultVar> = { rows: [ ...result rows... ], rowCount: 3 }Safety
- The connection host is checked — an author can't point it at
localhost, a private IP, or cloud metadata (the same SSRF guard as the HTTP and MongoDB nodes). Self-hosters can opt out withFLOWRUNNER_ALLOW_PRIVATE_EGRESS=1. - The node does not auto-retry — a failed
INSERT/UPDATEisn't safe to replay blindly. Wire the error port to handle failures.
Config
| Field | Required | Description |
|---|---|---|
connectionString | Yes | Postgres connection string, as a secret reference. |
query | Yes | SQL with $1, $2, … placeholders for values. |
params | No | One value per line — a flow reference (bound) or a literal. |
resultVar | No (default rows) | The variable the { rows, rowCount } result is written to. |
Ports
next and error.
Examples
Look up a customer by an id from the request:
text
SQL connectionString: ${secrets.DATABASE_URL}
query: "select id, email, plan from customers where id = $1"
params: [ start.customerId ]
resultVar: customer
# → refs.customer.rows[0].emailInsert a row and read the generated id back:
text
SQL query: "insert into orders (customer_id, total) values ($1, $2) returning id"
params:
start.customerId
start.total
resultVar: created
# → refs.created.rows[0].idTIP
Store the connection string once under Secrets as DATABASE_URL. Never build a query by pasting ${start.field} into the SQL — use $1 and a parameter, so form input can't inject SQL.