Someone drops a 400 character SELECT with three joins into a ticket, no line breaks anywhere. Paste it below and read it back as indented clauses, with every string literal, comment and quoted identifier exactly as it arrived.
Most browser SQL formatters are a stack of regular expressions. They uppercase every word on a keyword list, drop a newline in front of FROM and WHERE, then squeeze runs of whitespace down to a single space. The query is read as one flat string, and a flat string has no idea where a literal begins.
WHERE subject = 'order by priority' comes back with the literal rewritten to 'ORDER BY PRIORITY'. Run that against your database and the row count changes. Load the Literal traps sample above to watch the same query survive here.-- note absorbs whatever followed it, because the line break that ended the comment is gone. Output that looks tidy on screen deletes half the WHERE clause when it runs.`group by` is legal in MySQL. A formatter blind to quoting splits it across two lines and recases both halves.'O''Brien' is one literal holding an apostrophe, and MySQL also accepts 'O\'Brien'. A scanner that stops at the first apostrophe reads the rest of the statement as string content, so the indentation past that point is fiction.A ten second test for any SQL formatter. Paste SELECT 1 -- 'unclosed on one line and SELECT 2; on the next. If the output pulls the second SELECT up onto the comment line, that tool is collapsing whitespace before it reads comments, and the query it hands back is no longer the query you gave it.
Here is a monthly revenue query as it usually arrives, copied out of application logs with the line breaks stripped, next to the default output on this page.
select o.id,c.name,sum(l.qty*l.unit_price) as total from orders o join customers c on c.id=o.customer_id where o.status='paid' and o.placed_at>='2026-01-01' group by o.id,c.name having sum(l.qty*l.unit_price)>250;SELECT
o.id,c.name,SUM(l.qty * l.unit_price) AS total
FROM orders o
JOIN customers c
ON c.id = o.customer_id
WHERE o.status = 'paid'
AND o.placed_at >= '2026-01-01'
GROUP BY
o.id,c.name
HAVING SUM(l.qty * l.unit_price) > 250;Notice what stayed on one line. The join condition, the aggregate and the HAVING test never needed their own rows, so they did not get them. Line breaks land where a list crosses a clause boundary, which is where a reader looks for them.
| Control | Effect on the output |
|---|---|
| Dialect | Decides which characters open a quoted identifier and whether # starts a comment. It changes how the input is read, not how it is arranged. |
| Indent | Two spaces, four spaces or a tab per level. Tabs keep diffs quiet in a repository that already uses them. |
| Keywords | UPPERCASE, lowercase, Capitalized or left alone. Reserved words, type names and built-in functions change. Identifiers do not. |
| Commas | Trailing closes each line. Leading opens the next one, which makes a missing comma in a forty column select list obvious at a glance. |
| Blank line | Adds one empty row after each semicolon, so a migration script reads as separate steps rather than a wall. |
If your team has no house style yet, four spaces with trailing commas is the one to commit. It matches what most review tools and published SQL style guides already assume, and it survives being pasted into a ticket. Leading commas earn their keep once select lists run past twenty columns.
Two passes, both in your browser.
The first pass reads the input one character at a time and sorts it into tokens: line comments, block comments, string literals, quoted identifiers, numbers, bind parameters, words and operators. Quoting rules follow the dialect chosen in the toolbar, so a backtick opens an identifier under MySQL and a dollar quoted block stays intact under Postgres. Once a run of characters lands inside a comment or a literal, nothing looks at it again.
The second pass arranges those tokens. Clause keywords such as SELECT, FROM, WHERE and GROUP BY start a line at the current block indent. A JOIN starts a line and its ON condition drops one level in, alongside the AND and OR conditions it reads with. A comma list breaks across rows only when the list runs to the end of its clause, which is why COUNT(*) and IN ('web', 'app') keep their commas inline while a five column select list stacks. Subqueries indent from the line that opened them instead of from the left margin, so AND user_id IN (SELECT ...) stays readable three predicates deep.
Casing happens at token level. A word is recased only when it matches the reserved word, data type or built-in function list. Table aliases, column names and every character between quotes are copied through unchanged.
Nothing is uploaded. No request carries the query text, no copy is stored, and the page keeps working with the network switched off after the first load. The counters below the panes read the finished output, where the statement figure counts top level semicolons and the longest line figure tells you whether the result fits your editor without wrapping. For the reverse trip, the SQL minifier takes the layout back out again.
It arranges text. It does not understand your schema, and it will happily return a broken query in beautiful shape.
$$ ... $$ block is held as one literal. That protects the body from being mangled and also means the SQL inside it goes unformatted. Format the body on its own if it needs the work.key becomes KEY under the uppercase setting. Quote it and the formatter leaves it alone, which is the same reason your database wants it quoted.What the tool touches, what it refuses to touch, and where the dialect setting matters.
No. Whitespace, line breaks and keyword casing are the only edits, and casing skips anything inside quotes or comments. SQL ignores the whitespace between tokens and treats unquoted keywords as case insensitive, so the parsed meaning is identical before and after.
The one your database speaks. The setting decides which characters open a quoted identifier and which start a comment. MySQL reads backticks as quotes and a hash as a comment, SQL Server uses square brackets, Postgres and SQLite use double quotes, and Postgres also recognises dollar quoted blocks. Picking the wrong one still produces output, but a quoted name holding a space or a keyword gets read as ordinary words.
No. Tokenizing and layout run inside the browser tab. The query text never leaves the page, nothing is logged or stored, and the tool works offline once the page has loaded.
Subqueries indent from the line that opens them, not from the left margin. A subquery inside a WHERE predicate therefore sits deeper than one that follows FROM, which keeps the bracket pair visually attached to the condition it belongs to.
Yes. Top level semicolons split the script, the statement counter reports how many were found, and the blank line option separates them in the output. Semicolons inside string literals are ignored, so a payload containing one will not split anything.
Format keeps them. A comment on its own line stays on its own line, a trailing comment stays at the end of its line, and block comments are copied verbatim. Minify is the one mode that discards them, along with all the whitespace it removes.
Built-in functions are. COUNT, COALESCE, ROW_NUMBER, DATE_TRUNC and their neighbours follow the keyword setting. A function you or your team wrote is left exactly as typed, since it is not on the list.
The count of opening and closing parentheses did not match, usually because the paste was truncated. Indentation past the unmatched bracket is a guess at that point, so fix the query first and reformat rather than trusting the shape of the output.