PostgreSQL Query Formatter
Every character you typed survives -- this pass changes layout, never content. Pick PostgreSQL in the dialect box for queries like these and two readings flip in your favour: a bare # becomes integer XOR instead of a MySQL line comment, and a backslash inside single quotes stays a literal character, matching standard_conforming_strings (the default since 9.1). Everything else is the same engine-agnostic layout in every mode: JSONB path operators (#>, #>>, #-), -> and ->>, :: casts, DISTINCT ON, ILIKE, dollar-quoting and OVER (PARTITION BY ...) come through formatting and minifying intact.
What picking POSTGRES actually does
This formatter is one layout engine, not three parsers, and it will not rewrite your SQL into another dialect. Picking POSTGRES changes one thing: how two spellings that mean opposite things in two engines get read from the text — and it moves 3 of them away from the Auto reading. Everything on this page came out of that engine at build time, in this mode, so you can paste the same input and check it line for line.
A bare # between two operands
PostgreSQL spells integer bitwise XOR with #. MySQL reads the same byte as a line comment running to end of line. No parser can tell the two apart from the text alone, so the mode you pick decides which one survives.
Typed in
SELECT a # b FROM t;
Formatted in POSTGRES mode
SELECT a # b FROM t;
The same text in Auto (MySQL-safe) mode
SELECT a # b FROM t;
The same text in MySQL / MariaDB mode
SELECT a # b FROM t;
The same text in SQLite mode
SELECT a # b FROM t;
Minified in POSTGRES mode
SELECT a # b FROM t;
Minified in Auto (MySQL-safe) mode
SELECT a
Minified in MySQL / MariaDB mode
SELECT a
Minified in SQLite mode
SELECT a
This mode keeps # as an operator, so b FROM t stays attached to the expression. Under Auto or MySQL the same bytes comment out the rest of the line and that tail is simply gone.
A # comment above a clause
The same bytes are a comment in two of these modes and an operator in the third, and the difference is visible in where FROM t ends up.
Typed in
SELECT 1 # section FROM t;
Formatted in POSTGRES mode
SELECT 1 # section FROM t;
The same text in Auto (MySQL-safe) mode
SELECT 1 # section FROM t;
The same text in MySQL / MariaDB mode
SELECT 1 # section FROM t;
The same text in SQLite mode
SELECT 1 # section FROM t;
Minified in POSTGRES mode
SELECT 1 # section FROM t;
Minified in Auto (MySQL-safe) mode
SELECT 1 FROM t;
Minified in MySQL / MariaDB mode
SELECT 1 FROM t;
Minified in SQLite mode
SELECT 1 FROM t;
Nothing here is a comment in PostgreSQL, so # section stays inside the select list as an expression. Auto lifts it onto its own line because it takes it for a comment.
A string literal ending in a backslash
The classic Windows-path case. If a backslash escapes the next character, the first literal runs past its closing quote and fuses with the next one — and the text between the quotes starts being treated as code.
Typed in
SELECT 'C:\' AS p, 'a FROM b' AS q FROM t;
Formatted in POSTGRES mode
SELECT 'C:\' AS p, 'a FROM b' AS q FROM t;
The same text in Auto (MySQL-safe) mode
SELECT 'C:\' AS p, 'a FROM b' AS q FROM t;
The same text in MySQL / MariaDB mode
SELECT 'C:\' AS p, 'a FROM b' AS q FROM t;
Minified in POSTGRES mode
SELECT 'C:\' AS p, 'a FROM b' AS q FROM t;
Minified in Auto (MySQL-safe) mode
SELECT 'C:\' AS p, ' a FROM b ' AS q FROM t;
Minified in MySQL / MariaDB mode
SELECT 'C:\' AS p, ' a FROM b ' AS q FROM t;
With standard_conforming_strings on — the default since PostgreSQL 9.1 — a backslash is just a character, so the first literal ends at its own closing quote and a FROM b stays a value of its own. The Auto reading fuses the two literals and reflows FROM inside what was a single string.
What does not change
POSTGRES constructs come through formatting and minifying with the same characters they went in with, in every mode. That is deliberate — the layout pass is engine-agnostic — but read it as "your tokens survive", never as "the tool understands POSTGRES". It cannot validate anything, and broken SQL will be prettified into something that looks correct.
- JSONB path operators,
->and::casts:SELECT data->>'name', meta#>'{a,b}'::text FROM t WHERE j#-'{x}'; DISTINCT ON:SELECT DISTINCT ON (a) a, b FROM t ORDER BY a;
ILIKE:SELECT * FROM t WHERE n ILIKE '%x%';
- dollar-quoting:
SELECT $$body $$ AS b FROM t;
RETURNING:INSERT INTO t (a) VALUES (1) RETURNING id;
One warning that applies in every mode
Minify is not a shrinker of meaning. It collapses the statement onto one line and deletes every comment style — --, # and block — so anything an audit trail or an EXPLAIN annotation relies on is gone rather than compressed.
SELECT 1 FROM t;
More from SQLFormat
Guides
Frequently Asked Questions
Why does my PostgreSQL query lose everything after a # sign?
Because that # was read as a MySQL line comment. Auto keeps MySQL readings, so a bare # between two operands turns everything after it on that line into a comment: formatting keeps it, visibly, as a comment, and minifying deletes it outright. Pick PostgreSQL in the dialect box and # is integer bitwise XOR, so the expression stays attached and FROM moves back onto its own line. If your query has no # operator, the two modes give identical output.
Will the formatter break a Windows path stored in a string literal?
Not in PostgreSQL mode. With standard_conforming_strings on, which has been the default since 9.1, a backslash is an ordinary character, so a literal ending in one stops at its own closing quote. In Auto or MySQL mode that backslash escapes the closing quote instead, the literal fuses with whatever follows it, and the text between the two quotes gets reflowed as though it were code. Paste the same input in both modes to see it.
Does choosing PostgreSQL rewrite my SQL into standard SQL?
No, and that is deliberate. This is one layout pass that changes whitespace and keyword case, never tokens. DISTINCT ON, ILIKE, dollar-quoting, :: casts and JSONB path operators come through character-for-character in every mode, and nothing is validated — invalid SQL will be prettified into something that looks correct. Run it against your database before trusting it.
🧭 Pro Tips & Gotchas
<!-- Per-route Pro Tips for /sql/postgres-formatter/. Deliberately does NOT repeat
content/sqlformat.md (the homepage panel): keyword-anchored layout, the
uppercase-identifier trap, window-clause reflow, comment deletion and the
"no parser" caveat all live there and are linked instead. Duplicating that
text here is what made the three dialect routes ~92% identical. Every bullet
below was run against apps/sqlformat/src/core.js before it was written. -->
The two readings that flip in this mode
- A bare
#is integer bitwise XOR here.SELECT a # b FROM tstays one expression andFROMmoves back onto its own line; in Auto or MySQL the same bytes become a line comment and everything after the#leaves the statement. - A backslash inside single quotes is just a character, matching
standard_conforming_strings, which has been the default since PostgreSQL 9.1. A literal ending in a backslash stops at its own closing quote instead of running on into the next literal. - If your query contains neither spelling, this mode and Auto produce identical text. Picking PostgreSQL is not a formatting preference.
Things this pass does that are worth a look before you commit
::gains spaces in minified output:data->>'k'::textcomes back asdata ->> 'k' :: text. It still runs, but a diff that renames every cast will look like a larger change than it was.$$ … $$is read as one literal, so a--inside it stays text rather than commenting out the rest of the line. The literal keeps its internal newline, so a multi-line dollar-quoted block still breaks the output visually even though nothing is lost.- JSONB path literals keep their commas exactly.
meta#>'{a,b}'minifies with the path unchanged — it used to come out as{a, b}, a space inside the path that made the lookup wrong. Worth knowing because the bug was invisible on screen. DISTINCT ON (a)andILIKE '%x%'survive formatting and minifying with the pattern literal intact.
Where Postgres syntax gets layout, not understanding
RETURNING breaks onto its own line and a LATERAL subquery indents because both match as keywords, not because anything here knows PostgreSQL. Nothing is validated: a query with a missing comma comes out looking tidy and still fails at runtime. For anything machine-generated, use a real formatter and keep this for interactive work — see the [dialect differences guide](/guides/postgres-vs-mysql-dialects/) for the full list of what each engine means by the same character.