PostgreSQL SQL Formatter
Paste a Postgres query and get clean, consistently indented SQL. Numbered $1 placeholders from prepared statements are detected so you can fill them in, and Postgres-specific syntax such as JSONB operators, casts and dollar-quoted function bodies is preserved.
A PostgreSQL query, before and after
The unformatted query below is the sample loaded into the editor above, and the output is exactly what this formatter returns for it with the default options. Nothing was tidied up by hand.
WITH recent AS (SELECT user_id, count(*) AS n, max(created_at) AS last_seen FROM events WHERE created_at >= $1 AND payload->>'type' = $2 GROUP BY user_id) SELECT u.id, u.email, r.n, row_number() OVER (ORDER BY r.n DESC) AS rank FROM users u JOIN recent r ON r.user_id = u.id WHERE u.status = 'active' AND u.created_at::date BETWEEN $3 AND $4 ORDER BY r.n DESC LIMIT 50 WITH
recent AS (
SELECT
user_id,
count(*) AS n,
max(created_at) AS last_seen
FROM
events
WHERE
created_at >= $1
AND payload ->> 'type' = $2
GROUP BY
user_id
)
SELECT
u.id,
u.email,
r.n,
row_number() OVER (
ORDER BY
r.n DESC
) AS rank
FROM
users u
JOIN recent r ON r.user_id = u.id
WHERE
u.status = 'active'
AND u.created_at::date BETWEEN $3 AND $4
ORDER BY
r.n DESC
LIMIT
50 PostgreSQL syntax it handles
A generic formatter mangles dialect-specific syntax or refuses it outright. These are the PostgreSQL constructs this formatter is tested against.
$1, $2 Numbered placeholders from prepared statements are detected and can be filled in to produce a runnable query.
payload->>'key' JSON and JSONB operators are left exactly as written.
value::type Cast syntax is never mistaken for a :name parameter.
$$ ... $$ Dollar-quoted function bodies are treated as literals, so keywords inside them are not reformatted.
WITH ... AS Every CTE's WHERE clause is listed separately and can be edited on its own.
OVER (PARTITION BY ...) Window functions and their frames are formatted without breaking the clause apart.
PostgreSQL formatter FAQ
Does it handle $1 placeholders from prepared statements?
Yes. Postgres numbered placeholders are detected and listed in the Parameters panel. Fill each one and switch the output to filled SQL to get a query you can paste straight into psql.
Will it break my ::casts or JSONB operators?
No. Casts are distinguished from :name parameters, and JSON/JSONB operators such as -> and ->> pass through unchanged.
Can it format a query with several CTEs — and edit just one of them?
Yes. Every WHERE clause in the query is listed by the CTE it belongs to, so a query built from a chain of WITH blocks gives you one entry per block. Pick one, edit it as an AND/OR tree or a node graph, and the change is spliced back into that exact clause while the rest of the query is left byte-for-byte alone.
Will it format an incomplete query, or just a bare WHERE clause?
Yes. Nothing here depends on a complete, valid parse, so you can paste a fragment pulled out of a log line or an ORM debug dump — even a lone WHERE created_at >= $1 AND status = $2 — and still get it formatted and visually editable.
Does it keep PL/pgSQL bodies inside dollar quotes intact?
Yes. A dollar-quoted body is treated as a single literal, so the keywords inside it are not re-indented as though they belonged to the outer statement, and an opening tag is matched only against its own closing tag rather than the next dollar sign it finds.
Can it minify a Postgres query back down to one line?
Yes — beautify and minify are both available. Minifying is whitespace-aware rather than a blanket regular expression, so spacing inside string literals is preserved and the minified query still matches exactly the same rows as the one you started with.