Skip to the tool
makeshortwork.com SQL Formatter

SQL Formatter & Beautifier

Paste a query, choose your dialect, and read it as properly indented SQL with subqueries and CTEs nested where they belong. Nothing is uploaded — the formatter runs in this tab.

Formatted SQL
 

Runs entirely in this tab — your query is never uploaded.

Why formatted SQL is a code review problem, not a taste problem

SQL is the last part of most codebases where formatting is still argued about instead of automated. Application code goes through Prettier, gofmt, Black or rustfmt before it ever reaches a pull request. The query embedded three levels down in a repository file, or the migration someone pasted out of a database client, arrives however it happened to be typed: one 900-character line, or forty lines indented by whatever the editor felt like doing.

The cost shows up in review. A reviewer reading an unformatted query cannot see the shape of it — which conditions belong to the ON clause and which to the WHERE, whether that OR is inside the parentheses or outside them, how many tables are actually joined. Those are precisely the mistakes that produce a query that runs, returns plausible-looking rows, and is wrong. An OR that escaped its parentheses does not throw an error; it quietly returns the whole table.

Consistent formatting also makes version control useful again. When every query in the repository breaks lines at the same places, changing one condition produces a one-line diff. When they do not, adding a column to a SELECT list rewrites the entire statement in the diff and the reviewer has to read all of it again to find the change. That is the single biggest practical argument for running SQL through a formatter: not beauty, but small diffs.

The style conventions that fight each other

There is no standard SQL style guide, so teams inherit conventions from whoever wrote the first migration. Four disagreements come up again and again, and it is worth knowing what each side is actually optimising for.

ChoiceArgument forArgument against
Uppercase keywords Keywords stand out from identifiers without syntax highlighting — in a diff, a log, a terminal Shouting; irrelevant when the editor colours keywords anyway
Leading commas Commenting out or deleting a line never breaks the line above it Reads oddly to anyone who learned it the other way
River / right alignment Keyword column and value column separate cleanly Every rename re-aligns the whole block, producing huge diffs
One condition per line Each predicate is an independent, reviewable line Short queries become tall for no benefit

The leading-comma argument is the one people underestimate, so it is worth spelling out. With trailing commas, the last item in a list has no comma. Comment that last line out and the line above it now ends in a dangling comma, which is a syntax error in most engines. You have to edit two lines to remove one. With leading commas the comma belongs to the item that follows it, so every line is self-contained: comment out any line, including the last, and the remaining list is still valid. When you are bisecting a broken report by removing columns one at a time, that difference is the whole ballgame.

The counter-argument is real too — leading commas look wrong until they do not, and mixing both styles in one repository is worse than either. That is why the comma position here is a switch and not an opinion baked into the output: pick the one your team already uses and the formatter stops being a source of diff noise.

Dialect differences that actually change the formatting

Most SQL keywords are shared across engines. What is not shared is how each engine delimits things, and delimiters are exactly what a formatter has to get right. A single wrong assumption turns a column name into a string, or a string into code.

How this formatter avoids corrupting your query

Every formatter bug that matters has the same shape: something inside a literal got treated as code. The classic report is a WHERE note = 'SELECT FROM' that comes back reformatted, re-cased, or split across lines — a query that still runs and now matches nothing. So the first thing that happens here is a full tokenizer pass, before any decision about layout is made.

The tokenizer walks the text character by character with explicit state. Inside a string it knows that two consecutive quotes are an escaped quote and not the end of the literal, which is the exact point where naive implementations terminate early and corrupt everything after. Inside a line comment it knows that an apostrophe is just an apostrophe. Inside a delimited identifier it knows which closing character to look for, which depends on the dialect you selected. Only tokens classified as unquoted words are ever eligible for a case change, and only if they are in the reserved-word list.

Function names get one extra condition: they are re-cased only when a ( follows immediately. Plenty of real schemas have a column called count, replace or position, and there is no reason to shout at them.

Two behaviours follow from treating this as a safety problem rather than a rendering problem. First, if the tokenizer cannot finish — an unterminated string, an unclosed block comment, an identifier that never closes — nothing is formatted at all. You get your input back untouched, plus the reason. A partially formatted query that looks finished is worse than no output, because it invites you to copy it. Second, once a result is produced, it is tokenized again and compared token for token against the input. If anything was lost, duplicated or reinterpreted, the result is discarded and the original is returned. Formatting a query is a convenience; corrupting one that then runs against production is real damage, and the two are not worth trading.

Reading the structure: CTEs, subqueries, JOIN … ON and CASE

Indentation earns its keep at exactly four places, and those are the places this formatter spends its complexity on.

Subqueries and CTEs get a nested level. An opening parenthesis whose first meaningful token is SELECT or WITH becomes a block: it opens at the end of the current line, its contents are indented one level, and the closing parenthesis sits alone at the indentation of the line that opened it. Parentheses that are function calls or IN (…) lists stay inline, because breaking them adds height without adding information. That single distinction is what makes a three-CTE query readable at a glance — you can see where each block starts and ends without counting brackets.

JOIN … ON puts the join on its own line at clause level and the condition one level in. When a join has several conditions, each AND lands on its own line below the ON, so it is immediately visible whether a predicate belongs to the join or to the WHERE — a distinction that changes results on outer joins and is invisible in a single-line query.

CASE expressions put every WHEN and the ELSE on their own line, with END back at the level of the CASE. A five-branch CASE written inline is unreadable and a five-branch CASE written this way is a small table.

Long column lists break one item per line, but only when there is more than one item. ORDER BY created_at DESC stays on one line; a fifteen-column SELECT becomes fifteen lines. Breaking a single-item clause is the kind of rule that makes formatted SQL twice as tall as it needs to be.

The AND inside BETWEEN … AND … is deliberately not treated as a boolean connector, because it is not one — it is part of the operator. Formatters that break on it produce a dangling line that reads like a separate condition.

Why pasting production SQL into an upload-based tool is a problem

Most online formatters post your text to a server, format it there, and send the result back. For a colour picker that would be a curiosity. For SQL it is a disclosure.

A production query is a compact description of your system. It names your tables and columns, which is a schema leak. It encodes business logic — how a customer is classified, what counts as churn, which flag suppresses a charge. And the literals in the WHERE clause are frequently live data: an account id, an email address, a document number someone was investigating when the query stopped working. That combination is what a security review calls a data disclosure, and it happens through a text box, without a file, so nothing in a DLP pipeline notices.

The practical consequence is not that formatting is dangerous, but that the processing location decides whether it is. Here the formatter is a JavaScript module delivered with the page. It has no network code in it, and you can verify that: open the network tab, paste a query, format it, and watch nothing leave. You can also load the page, disconnect, and keep formatting. If a tool cannot survive that test, it is uploading your query, whatever its privacy page says.

What this tool deliberately does not do

Being explicit about the limits is more useful than implying they do not exist:

Frequently asked questions

Is my SQL sent to a server?

No. The tokenizer and the formatter are plain JavaScript that ship with the page and run inside your tab. There is no upload, no API call and no logging. That matters more for SQL than for most formats: a query usually reveals your table names, your column names and a good part of your business rules, and often carries real customer values in the WHERE clause.

Will it ever change what my query does?

It is designed not to. Formatting only rewrites whitespace and the letter case of reserved words; the content of string literals, comments and delimited identifiers is copied byte for byte. Before showing you anything, the tool re-tokenizes its own output and compares it token by token with your input. If a single token differs, the result is thrown away and your original SQL comes back with an explanation instead.

What happens if my SQL has a syntax error?

It depends on the kind of error. This is a formatter, not a parser with a grammar, so an unbalanced GROUP BY or a missing table alias will still be formatted — the tool has no way to know the query is wrong. What it does refuse is anything it cannot tokenize safely: an unterminated string, an unclosed block comment, an identifier whose closing delimiter never arrives. In those cases nothing is changed and you get a message saying why.

Which dialect should I pick?

Pick the database you actually run the query against. The dialect setting is not cosmetic: it decides what a double quote means (an identifier everywhere except MySQL, where it is a string), whether backticks and square brackets delimit identifiers, whether # starts a comment, and whether dollar-quoted strings exist. Choosing the wrong one is the fastest way to make a formatter mangle a literal.

Why does Minify keep a line break after a comment?

Because a -- comment runs to the end of the line. Collapsing a query with a line comment onto a single line would comment out everything after it, turning a working statement into a broken one. So the minifier keeps exactly one newline after each line comment and removes every other break. Block comments have no such problem and stay inline.