SQL steps in flows
A SQL step runs one query on one of the project’s PostgreSQL or MySQL connections, and its rows become the step’s output — ready for a loop, a Transform, an e-mail or a script below. With a SQL step, a loop and a Send email step, a whole campaign runs with no script at all.
- Add a SQL step and give it a connection
- Pass flow values into the query as :name parameters
- Read the output, the row limit and the limited flag
- Loop over the rows, including results too large to pass inline
A query as a step
In the Add Node menu, PostgreSQL and MySQL sit beside the script languages under Scripts. Picking one adds a step whose code is SQL; Existing Script links a SQL script of the project instead. Either way the step runs on the database through the project’s connection — the credentials never reach your code — and answers with its rows as objects keyed by column name.
{
"columns": [
"region",
"total"
],
"rows": [
{
"region": "South",
"total": 1840.5
},
{
"region": "North",
"total": 1312
}
],
"rowCount": 2,
"count": 2,
"durationMs": 4,
"warnings": [],
"limited": false,
"limit": 10000
}Numeric, date, time, timestamp and UUID columns are decoded into JSON values — a numeric total arrives as a number — so the next step can bind rows.0.total straight away.
Give it a connection
- Add the step
Click a + and pick PostgreSQL or MySQL under Scripts.
- Open its code
Click the step, then Open code editor. Write the query; parameters you declare appear on the Parameters tab.
- Pick the connection
On the Connection tab, choose a connection of the same engine under For this step. A linked SQL script shows its own default (Script default: crm-db); a connection picked on the step wins over it, and Use the script default goes back.
- Set the row limit
Row limit, on the same tab, is how many rows the query may return — 10,000 unless you change it, up to 100,000.
Parameters in a query
Declare each input in a -- @param header and use it as :name in the statement. The value is sent to the database as a real bound parameter, never pasted into the text, so a value can never change the query. On the step, bind each parameter like any other input — From node, Expression, Variable or a constant.
-- @param min_total number = 100
-- @param country string = BR
SELECT id, email, first_name AS "First Name", total
FROM customers
WHERE active AND total >= :min_total AND country = :country
ORDER BY total DESC;The row limit and “limited”
A read — a statement starting with SELECT or WITH — is wrapped so the limit holds whatever the query looks like: SELECT * FROM (<your query>) AS _waiso LIMIT n+1. Your own LIMIT still wins when it is smaller. The extra row tells “exactly n” from “more than n”: when there were more, the output says limited: true with the limit, and the step’s header in the run reads Limited to 10,000 rows. Writes (INSERT, UPDATE, DELETE…) are never wrapped; rows they return are still capped.
| Where | Rows returned |
|---|---|
| A SQL step in a flow (and a SQL script run) | 10,000 by default, up to 100,000 |
| The SQL Workbench | 500 — it is for looking, not for moving data |
Looping over the rows
Set a loop’s Items to From Node → the SQL step and it loops over rows by itself; the panel shows the columns (Each row: id · email · First Name). Inside the body, bind item.email, item["First Name"] and index. A parallel loop with a Send email step inside sends the whole list in waves of up to 20.
Large results
Rows are passed between steps inline up to 5 MB. A result larger than that is stored in the project’s S3 bucket — or the platform’s, if the project has none — and the output changes shape: rows is empty, preview holds the first 100 rows, offloaded is true, and downloadPath points at the whole result. The step’s panel says Large result — it travels between steps through S3.
{
"columns": [
"id",
"email"
],
"rows": [],
"preview": [
{
"id": 1,
"email": "ana@example.com"
}
],
"offloaded": true,
"sizeBytes": 7340032,
"rowCount": 61234,
"count": 61234,
"limited": false,
"limit": 100000,
"downloadPath": "/api/projects/<project>/runs/<run>/result"
}- A loop bound to it still loops over every row: it reads them from storage a page of 1,000 at a time.
- A script step bound to
rowsreceives the stored result’s reference, with itsdownloadPath, instead of the rows — and reads the rows from there. downloadPathanswers with a short-lived download link; when the stored object is gone it answers410 result_unavailable.
Limits
- One statement per step for the limit to apply: a script with several statements is run as written and only its returned rows are capped.
- Row limit: 1 to 100,000 (10,000 by default). Inline results up to 5 MB; larger ones go to storage.
- The connection must be of the step’s engine — a PostgreSQL step cannot use a MySQL connection.