SQL Formatter: Make Any Query Readable
Paste a query that arrived as one long line and get it back laid out so a human can review it. This formatter splits your SQL into tokens before it changes a single space, which is why a keyword sitting inside a quoted string is left exactly where you put it. It runs in your browser and never uploads anything.
Everything runs in your browser — your query is never uploaded. Only reserved words are case-folded; table, column, and function names are left exactly as you typed them.
SELECT
u.id,
u.first_name,
u."Email Address",
o.total,
CASE WHEN o.total > 500 THEN 'vip' ELSE 'standard' END AS tier
FROM users u
INNER JOIN orders o
ON o.user_id = u.id
AND o.status <> 'cancelled'
WHERE u.country IN ('US', 'CA', 'GB')
AND u.note = 'select from where' -- keywords inside strings must survive
AND u.label != 'it''s fine' /* the '' above is an escaped quote, not the end of the string */
AND o.created_at BETWEEN '2026-01-01' AND '2026-06-30'
AND o.total > (
SELECT
avg(total)
FROM orders
WHERE status = 'paid'
)
GROUP BY u.id, u.first_name
ORDER BY o.total DESC
LIMIT 25;What this tool does — and what it deliberately does not
- It does not validate your SQL. This is a text formatter, not a parser. It will happily lay out a query that your database would reject, and it never tells you a query is “correct”. Run it against your database to find out.
- It is dialect-agnostic. MySQL quotes identifiers with backticks, Postgres and the SQL standard use double quotes, SQL Server uses square brackets — all three are recognised and passed through untouched, but nothing here is checked against a specific dialect’s grammar.
- String literals, quoted identifiers, and comments are never rewritten. A keyword inside
'select from where'stays lowercase and on the same line,''is treated as an escaped quote, and--and/* */comments are copied out byte-for-byte. - A
--comment keeps its line break, even when minifying. It runs to the end of the line, so collapsing it onto one line would comment out the rest of your query. Strip those comments first if you need a true single line.
TL;DR
A formatter that touches the inside of your string literals is worse than no formatter at all, because the query usually still runs and you find out weeks later. This one tokenizes first: strings, comments and quoted identifiers are copied out byte for byte, and only reserved words get case-folded. It formats text — it does not parse, validate, optimise, or explain your SQL, and it is not tied to one dialect. Use it to make a query reviewable; use your database to find out whether the query is right.
Why a formatter has to tokenize before it reformats
Most quick-and-dirty SQL beautifiers are a stack of regular expressions run over the raw text: uppercase anything that looks like a keyword, break a line before every FROM, collapse runs of whitespace. That works right up until the query contains a string, and then it quietly edits your data. The classic failure looks like this:
-- what you wrote WHERE note = 'select from where order by' -- what a regex formatter gives back WHERE note = 'SELECT FROM WHERE ORDER BY'
The comparison now fails against every row, and nothing errors. The fix is structural, not a longer regex: read the input character by character into typed tokens first — whitespace, line comment, block comment, string, quoted identifier, number, word, punctuation — and only then decide where the line breaks go. Four of those token types are emitted verbatim, so no layout rule can ever reach inside them. Here is the same query through this tool:
SELECT * FROM t WHERE note = 'select from where order by' AND tag = 'it''s fine';
The keywords inside the quotes stay lowercase and on one line, and 'it''s fine' is understood as one string containing an escaped quote rather than two strings with stray text between them. The same protection covers -- and /* */ comments, backtick and bracket identifiers, and PostgreSQL $$dollar-quoted$$ blocks.
The house style: what gets its own line
The layout here is opinionated on purpose. Leading keywords sit at column zero so you can read the shape of a statement down the left edge; selected columns get one line each so adding a column is a one-line diff; join conditions and boolean operators are indented one level under the clause they belong to. This is the full rule set:
| Construct | What happens |
|---|---|
| WITH · SELECT · FROM · WHERE · GROUP BY · HAVING · ORDER BY · LIMIT · OFFSET · WINDOW | Starts a new line at the current indent level |
| JOIN · INNER / LEFT / RIGHT / FULL / CROSS / NATURAL JOIN (and the OUTER variants) | Own line, matched longest-first so LEFT OUTER JOIN stays one phrase |
| ON | Own line, indented one level under its join |
| AND · OR | Own line, indented one level — except the AND in BETWEEN x AND y, and boolean operators inside a CASE expression, which stay inline |
| Column list after SELECT or SET | One item per line, indented one level |
| GROUP BY / ORDER BY lists, IN lists, function arguments | Deliberately kept inline — breaking these adds height without adding clarity |
| ( followed by SELECT or WITH — a subquery or CTE | Opens a new indent level; the closing ) returns to the column of the line that opened it |
| UNION / EXCEPT / INTERSECT (and the ALL forms) | Own line, so the seam between two queries is visible |
| INSERT INTO · VALUES · UPDATE · SET · DELETE FROM · RETURNING · ON CONFLICT · ON DUPLICATE | Own line — writes get the same treatment as reads |
| CREATE TABLE · CREATE VIEW · ALTER TABLE · DROP TABLE · TRUNCATE TABLE | Own line at column zero |
| ; | Ends the statement, resets the indent, and leaves a blank line before the next one |
| Reserved words | Case-folded to your setting. Table, column and function names are not — sum() stays lowercase if you typed it lowercase |
Indentation is 2 spaces, 4 spaces, or a tab, your choice. The minify mode applies the same tokenizer in reverse — one line, single spaces, nothing removed — with one exception: a -- comment keeps its newline, because collapsing it would comment out the rest of the statement.
The same query, before and after
This is what comes out of an ORM log, a stack trace, or a colleague’s chat message — a real query with three tables in it, on one line:
select o.id, c.name, sum(l.qty*l.price) as total from orders o join customers c on c.id=o.customer_id left join order_lines l on l.order_id=o.id where o.status='paid' and o.created_at >= '2026-01-01' group by o.id, c.name having sum(l.qty*l.price) > 100 order by total desc limit 20;
And this is the same query, unchanged in every way that matters, after formatting with uppercase keywords and 2-space indents:
SELECT o.id, c.name, sum(l.qty * l.price) AS total FROM orders o JOIN customers c ON c.id = o.customer_id LEFT JOIN order_lines l ON l.order_id = o.id WHERE o.status = 'paid' AND o.created_at >= '2026-01-01' GROUP BY o.id, c.name HAVING sum(l.qty * l.price) > 100 ORDER BY total DESC LIMIT 20;
Notice what did not change: sum is still lowercase because it is a function name, not a reserved word, and the GROUP BY list stayed on one line. Fourteen lines instead of one, and now you can see at a glance that there are two joins, both with an ON clause.
Formatting is a code-review tool, not decoration
A one-line query is unreviewable. Nobody reads 400 characters of horizontal text carefully, and in a pull request a one-line query produces a one-line diff: change a join condition and the reviewer sees the entire query marked as modified, with no way to tell what actually moved. Break it into clauses and the diff shrinks to the line that changed. That alone is worth the habit — commit your SQL formatted, and format it the same way every time so the diffs stay honest.
The most valuable thing formatting surfaces is the accidental cross join. Two tables listed in FROM separated by a comma, with no join condition, produce every row of one paired with every row of the other — 10,000 customers and 500 products is five million rows. In a wall of text it is invisible. On its own line it is obvious:
SELECT c.name, p.title FROM customers c, products p WHERE c.active = 1;
The tell is a FROM line carrying two comma-separated tables with no JOIN and no indented ON beneath it. The formatter does not flag this — it has no idea whether you meant it — but it puts the evidence where a human will see it. The same goes for a WHERE clause whose AND conditions stack vertically: a missing condition is a missing line, and missing lines are easy to notice.
Identifier quoting and quote escaping, dialect by dialect
The reason a shared formatter has to be dialect-agnostic is that the four mainstream databases disagree about the two characters that matter most to a tokenizer: what quotes an identifier, and how you put a quote inside a string. All of the forms below are recognised and passed through untouched.
| Database | Quoted identifier | Quote inside a string | Line comment |
|---|---|---|---|
| MySQL / MariaDB | `order` | '' or \' | -- or # |
| PostgreSQL | "Email Address" | '' | -- |
| SQLite | "col", also `col` and [col] | '' | -- |
| SQL Server | [Order Items] | '' | -- |
- Double quotes are the trap. In PostgreSQL, SQLite and the SQL standard,
"x"is an identifier. In MySQL’s default mode it is a string, unless the server is running inANSI_QUOTESmode. The same character means two different things, which is exactly why a formatter cannot assume a dialect. - Backslash escapes are MySQL-flavoured. MySQL treats
\'as an escaped quote by default; PostgreSQL does not inside an ordinary string, only inside anE'...'string. This tool honours the backslash form, which is the safe direction to be wrong in — see the failure note below. - Case folding differs too. PostgreSQL lowercases unquoted identifiers, so
Emailandemailare the same column but"Email"is not. The formatter never changes the case of an identifier, quoted or otherwise, precisely because that decision is not safe to make for you. - PostgreSQL dollar quoting is handled.
$$...$$and$tag$...$tag$blocks — the usual way to write a function body — are captured whole and emitted verbatim, so the SQL inside a function body is never reformatted out from under you.
If you are writing the schema rather than querying it, the sibling SQL CREATE TABLE generator does know about dialects: you design a table visually and it emits DDL with the correct identifier quoting, auto-increment syntax and type names for PostgreSQL, MySQL, SQLite or SQL Server. And if you are still deciding whether SQL is the right store at all, SQL vs NoSQL covers that trade-off.
What it will not do, and how it fails when it fails
This is a text formatter. It does not parse SQL into a syntax tree, so it cannot validate a query, cannot lint it, cannot suggest an index, cannot rewrite a subquery as a join, and cannot tell you anything about the execution plan. A query it lays out beautifully may still be rejected by your database on the first line. Run EXPLAIN against the real thing for anything performance-related — no browser tool can know your table sizes or your indexes.
Because it is dialect-agnostic, there are inputs where the tokenizer guesses wrong. Two worth knowing about, both PostgreSQL-flavoured:
- A
#is read as the start of a MySQL line comment, so the PostgreSQL JSON path operators#>and#>>cause the rest of that line to be treated as comment text. - A backslash immediately before a closing quote is read as an escape, so a literal Windows path like
'C:\'in a standard-conforming PostgreSQL string makes the tokenizer run on to the next quote.
In both cases the failure mode is the same and it is the important part: the affected text stops being formatted, and is copied out unchanged. You get a query that looks half-formatted, which is visible and fixable, rather than a query that looks perfect and means something different. A formatter that fails loudly is worth more than one that fails silently — which is the whole argument of this page. As a habit, glance at the output before you run it, the same way you would glance at any diff.
Frequently asked questions
Does formatting SQL change what the query does?
It should not, and this one does not. Formatting only moves whitespace around and changes the case of reserved words, which SQL treats as insignificant outside of quotes. Everything inside a string literal, a quoted identifier, or a comment is copied out byte for byte, so the sequence of tokens the database sees is identical before and after. Nothing is added, removed, or reordered.
Why do some SQL formatters break string literals?
Because they work on the raw text with regular expressions instead of tokenizing first. A naive formatter that uppercases every occurrence of the word select or breaks a line before every FROM has no way of knowing whether it is looking at a keyword or at the middle of a string like 'select from where'. It rewrites your data, and because the query usually still runs, you find out later. This tool splits the input into tokens before it makes any layout decision, so a string is never mistaken for a keyword.
Should SQL keywords be uppercase?
It is a convention, not a rule, and SQL is case-insensitive for keywords in every mainstream database. Uppercase keywords make the shape of a long query easier to scan because the clause words stand out from the table and column names. Whichever you pick, pick one and apply it everywhere in a codebase, because a mixed style makes diffs noisy. This tool offers uppercase, lowercase, or leave alone, and it only touches reserved words, never your identifiers or function names.
Which SQL dialect does this formatter support?
It is deliberately dialect-agnostic. It recognises the identifier quoting of MySQL backticks, PostgreSQL and SQLite double quotes, and SQL Server square brackets, plus PostgreSQL dollar-quoted strings, and it passes all of them through untouched. What it does not do is check your query against any dialect’s grammar, so it will happily format a query that your database would reject.
Is it safe to paste a production query into an online SQL formatter?
With this one, the formatting happens entirely in your browser: the query is never uploaded, logged, or sent to a server, and there is no account or saved history. That is not true of every online formatter, so if a query contains real table names, embedded credentials, or literal customer data, check where the tool runs before you paste it. When in doubt, replace the literals with placeholders first.
Does this tool check my SQL for errors or suggest indexes?
No. It is a text formatter, not a parser, a linter, or a query planner. It will not tell you that a column does not exist, that a join is missing a condition, or that a query needs an index. It only makes the query readable so that you can spot those things yourself, and so a reviewer can. For anything about execution cost, run EXPLAIN on the database itself.