SQL Formatter
Pretty-print or minify SQL across 13 dialects with configurable style — entirely on this device.
This tool formats — it does not validate. Formatted output can still fail to run.
Press ? for shortcuts
Do it offline:$ npx sql-formatter -l postgresql query.sql
How to use it
- Pick the dialect that matches your database — a banner proposes one when backticks, TOP n or $$ markers give it away, and nothing switches until you accept.
- Paste SQL or drop a .sql file; files are read locally, and anything over the 10 MB cap is refused by name rather than truncated.
- Tune keyword case, indent width or tabs, blank lines between statements and the inline-list width — each control previews its effect before you commit.
- Copy the result, download it as a .sql file, or open the Minify tab for a one-line version with its size readout.
Paste tangled SQL and read it back one clause per line. This page runs the sql-formatter library across 13 dialect grammars, uppercases keywords on request, and never rewrites what your quotes protect — string literals return byte-for-byte. A second tab minifies scripts to a single line and reports the byte saving. Both passes run inside a Web Worker on your device; your SQL is never uploaded.
How it works
Under Format, your text goes to the sql-formatter JavaScript library at a fixed, pinned version, so the same input keeps printing the same bytes visit after visit. The library tokenizes SQL rather than truly parsing it: it recognizes keywords, literals, comments and operators, then reprints that stream with fresh line breaks, indentation and casing. Tokenizing is also why this page cannot judge correctness — select * frmo users; formats without complaint, because to a tokenizer frmo is just another identifier.
Each of the 13 dialects loads its own grammar: MySQL treats backticks as identifier quotes and # as a comment marker, T-SQL accepts [bracketed names], PostgreSQL adds dollar-quoted strings such as $body$ … $body$. That is why the selector matters — a bracket-quoted column is a parse error under Standard SQL yet perfectly legal under T-SQL. Three cheap signals (backticks, TOP n, $$) drive the suggestion banner, which only ever asks.
The formatting library is heavy, so it loads as its own script chunk inside a Web Worker the first time you run it — off the page you first see and off the main thread while it works. Minify never touches that library at all: an in-house scanner splits the script into code, string and comment spans, collapses whitespace in code spans only, and copies every string span untouched. Neither pass performs a network request carrying your SQL.
Examples
select u.id, u.email, p.title, p.created_at from users u inner join posts p on p.user_id = u.id where u.active = 1 and p.created_at > '2025-01-01' order by p.created_at desc limit 20;SELECT
u.id,
u.email,
p.title,
p.created_at
FROM
users u
INNER JOIN posts p ON p.user_id = u.id
WHERE
u.active = 1
AND p.created_at > '2025-01-01'
ORDER BY
p.created_at DESC
LIMIT
20;One ORM-generated line becomes a readable statement: every clause starts a line, keywords go upper-case, and two-space indents mark what belongs to FROM versus WHERE. The date literal '2025-01-01' is untouched.
create function audit_note() returns trigger as $body$
begin
insert into audit(note) values (new.id || ' changed');
return new;
end;
$body$ language plpgsql;CREATE FUNCTION audit_note () returns trigger AS $body$
begin
insert into audit(note) values (new.id || ' changed');
return new;
end;
$body$ language plpgsql;Only the wrapper is restyled — CREATE FUNCTION and AS gain their casing while the $body$-quoted block, ragged un-indented lines included, returns byte-for-byte. returns trigger and language stay lowercase because the PostgreSQL grammar here does not list them as keywords to re-case.
SELECT
u.id,
u.email,
p.title,
p.created_at
FROM
users u
INNER JOIN posts p ON p.user_id = u.id
WHERE
u.active = 1
AND p.created_at > '2025-01-01'
ORDER BY
p.created_at DESC
LIMIT
20;SELECT u.id, u.email, p.title, p.created_at FROM users u INNER JOIN posts p ON p.user_id = u.id WHERE u.active = 1 AND p.created_at > '2025-01-01' ORDER BY p.created_at DESC LIMIT 20;Minify undoes the first example: 203 bytes of formatted SQL collapse to 183 on a single line, a 10% saving. Semicolons keep statements apart, and only whitespace between tokens is collapsed — never any inside quotes.
select id, 'DROP TABLE users' as attempted_command from audit_log where command like '%drop table%';SELECT
id,
'DROP TABLE users' AS attempted_command
FROM
audit_log
WHERE
command LIKE '%drop table%';A query about dangerous SQL is not dangerous SQL: 'DROP TABLE users' and '%drop table%' are string data, so both come back character-for-character while the keywords around them are re-cased.
Every example on this page is executed against the same engine that powers the tool, on every build.
Gotchas worth knowing
String literals are load-bearing
Anything inside quotes is data, never syntax: keyword casing, re-indentation and minification all skip string spans, quoted identifiers and PostgreSQL dollar-quoted bodies — the last keep even their internal line breaks, as the function example shows.
Comments are kept, and counted when removed
Format mode re-indents comments along with the code but never deletes one — -- and /* */ annotations both ride through. Minify here always strips them and says so with a count, 2 comments removed for two, so nothing vanishes without a receipt.
The dialect changes what parses
select [total amount] from invoices; fails under Standard SQL with Parse error: Unexpected "[total amo" at line 1 column 8. yet formats cleanly once T-SQL is selected, where brackets quote identifiers. Backticks and # comments are the MySQL-family equivalents. When a parse fails unexpectedly, trying the right dialect is the first fix.
One bad statement doesn't blank the rest
In a multi-statement script, everything before the first statement the parser rejects still formats; the remainder is appended exactly as you wrote it, flagged with statements after line 1 left unformatted plus the verbatim parse error, its caret frame and a jump to the failing line.
Hostile options degrade politely
A shared link or state file naming settings this page does not offer — a hand-edited indent of 12, an invented dialect — never aborts the restore: the page swaps in values it can represent and says so plainly with Some settings in this link were not recognized — defaults were used. Your SQL still loads and formats.
Invisible bytes get visible notices
A leading byte-order mark is dropped and reported as Leading byte-order mark removed; Windows line endings become LF with Line endings normalized to LF; an unpaired surrogate half is replaced by U+FFFD with a warning. A file that does not decode cleanly as UTF-8 raises a replacement-character notice too, instead of failing silently.
Big scripts run in a worker, up to 10 MB
Formatting happens off the main thread, with a busy indicator and Cancel for slow runs and an explicit Run button naming the size once input passes 2 MB. Past 10 MB of characters the tool refuses with a typed message instead of truncating — for dumps beyond that, the command line below is the right tool.
Common errors
Parse error: Unexpected "'abc from " at line 1 column 8.- An opening quote never found its partner, so the tokenizer swallowed the rest of the statement as one string and gave up. The position points at the quote itself — close it, or double it as '' if you meant a literal apostrophe.
Parse error at token: ; at line 1 column 25- The grammar ran out of legal continuations at the named token — here a semicolon arriving while count( was still open. The actual slip usually sits left of the caret; the reported position is where recovery became impossible.
Input is larger than the 10 MB limit for this tool.- A typed refusal, not a crash, and never a silent truncation — the editor keeps your full paste. Split the dump, or run the printed npx command, which has no such ceiling.
input contains an unpaired surrogate; it was encoded as U+FFFD- The paste carried half of a two-unit character — typical of a file sliced mid-emoji or decoded with the wrong charset. Output stays well-formed; search it for � to find the repaired spot.
FAQ
Does formatted SQL always run?
No — pretty output proves nothing about validity. The engine re-cases and re-indents tokens without checking that they form an executable statement, so select * frmo users; formats cleanly here and fails on any real database. A dedicated SQL syntax checker is planned as its own tool; until then, the database itself is the judge.
Is my SQL uploaded when I format it?
No request carrying your input leaves this page — formatting and minifying run in a Web Worker on your machine, which the network panel of your browser's devtools can confirm. Pastes that look credential-shaped are additionally kept out of between-visit storage.
Which SQL dialects does it understand?
13: Standard SQL, PostgreSQL, MySQL / MariaDB, SQLite, T-SQL, PL/SQL, BigQuery, Snowflake, Redshift, Spark, Trino / Presto, DB2 and HiveQL. Detection stays deliberately modest — backticks suggest MySQL, TOP n suggests T-SQL, $$ suggests PostgreSQL, scanning at most the first 256 KB — and a banner asks before any switch.
Can formatting corrupt my string literals?
Quoted regions are copied, not reprinted: every literal, quoted identifier and dollar-quoted body returns byte-identical, which the function example demonstrates with a $body$ block whose ragged inner lines survive. Every pair printed on this page is re-executed against the engine on each build, so that guarantee is tested rather than asserted.
What happens to comments when I minify?
They are removed, and the run reports how many, e.g. 2 comments removed — never a silent drop. Minify on this page has no keep-comments switch, so when annotations matter, stay in Format mode: it re-indents every -- and /* */ comment and deletes none of them.
Why did some keywords stay lowercase?
Upper-casing applies only to words the selected dialect's grammar tags as keywords. In the function example, returns trigger and language pass through under PostgreSQL while CREATE FUNCTION and AS convert. Pick preserve as the keyword case when nothing should be re-cased at all.
How large an input can I paste?
10 MB of text, checked before any work starts; over the cap you get the exact refusal quoted above and an unchanged editor. Minify's size readout counts UTF-8 bytes, so its saving matches what you would see on disk. For bulk dumps beyond the cap, pipe files through the npx command at the end of the page.
Keyboard shortcuts
- ⌘/Ctrl + Enter
- Run the current mode — the explicit path once inputs get big.
- ⌘/Ctrl + S
- Download the output as a .sql file.
- ⌘/Ctrl + C
- Copy the whole result when no text is selected.
- ⌘/Ctrl + Shift + C
- Copy the whole result even mid-selection.
- t
- Switch between light and dark theme.
- ? or ⌘/Ctrl + /
- Open the shortcut reference dialog.
- Esc
- Close the open dialog.