Loading SQL Formatter…
Paste a query to lay it out by clause.
The query is broken into tokens before anything is moved, so a keyword inside a string literal stays lowercase and a -- comment keeps the rest of its line to itself. Nothing between quotes is ever re-cased or re-wrapped: WHERE note = ‘select all’ comes back with its literal identical, character for character.
A query pasted out of an ORM log or a bug report arrives as one long line, and the first thing anybody does is hunt for the joins. This formatter puts every clause at the start of its own line — SELECT, FROM, each JOIN, WHERE, GROUP BY, HAVING, ORDER BY, LIMIT — indents each join condition under the join it belongs to, and breaks an AND or OR chain so the conditions stack one per line. Keywords are cased consistently: uppercase by default, lowercase or untouched if you prefer.
The result is not merely prettier. Once the conditions are stacked, a missing alias, a join predicate comparing a column to itself, or a filter that belonged in the ON clause becomes visible at a glance instead of hiding inside a 400-character line. Nothing is uploaded to do it, which matters when the query came out of a production log with real values in its literals.
The query is split into tokens before a single character is repositioned: string literals, quoted identifiers, comments, words, numbers, bind parameters and punctuation. Everything that came out of a pair of quotes is copied byte for byte and never re-cased or wrapped. A formatter built on search-and-replace eventually uppercases a keyword inside a literal, and the query it produces still runs — it simply returns different rows, which is the worst failure available.
So WHERE status = 'select from where' keeps its literal exactly as typed, a doubled apostrophe in 'it''s' is read as an escaped quote rather than the end of the string, and MySQL backticks, SQL Server brackets and double-quoted identifiers all survive intact. A -- comment keeps the remainder of its line, and a /* */ comment moves as one unit with its text untouched.
A select list of two columns stays where it is; a list of four or more, or one long enough to overflow a sensible line, is broken one column per line and indented. Applying that only above a threshold keeps SELECT id, email compact while giving a twelve-column report the vertical layout it needs.
Subqueries are indented by nesting depth. An opening bracket followed by SELECT starts a new indented block with the closing bracket on its own line, so a derived table inside a derived table steps out two levels and back in as it closes. Brackets that are not subqueries stay inline, which is what you want for COUNT(*), COALESCE(a, b) and IN (1, 2, 3). The exception is an INSERT column list, which keeps a space after the table name so users (id, email) does not read as a function call.
What you get is a layout, not an analysis. It does not validate the query, resolve table names or check that a column exists; a query that will fail against your database is formatted neatly and still fails. It rewrites nothing semantic — no reordered conditions, no invented aliases, no JOIN turned into a WHERE — so the statement that goes in is the statement that comes out.
Two limits are worth stating plainly. Keyword casing works from a fixed list, so an identifier that happens to be a reserved word, such as a column literally named order, is uppercased along with the real keywords; quote it, or switch keyword casing off, if that matters. And a long CASE expression stays on one line rather than splitting at every WHEN, because breaking it reliably means tracking where the expression ends, and getting that wrong is worse than a long line.
Not in the ways that matter: whitespace between tokens has no meaning in SQL, and nothing inside quotes or comments is altered. The one visible change is keyword casing, which the parser also ignores unless an unquoted identifier of yours collides with a keyword.
Because it appears in the keyword list — names like order, key, values and count are all reserved words in at least one dialect. Wrap it in double quotes, backticks or brackets and it will be treated as an identifier, or set keywords to be left as typed.
It handles what they have in common plus each one’s quoting and parameter styles: backticks, square brackets, double quotes, :name, $1 and ?. Dialect-specific statements it does not recognise as clauses pass through as ordinary tokens rather than being mangled, so nothing is lost where the layout is plainer.
The bracket is inspected: if the first thing inside it is SELECT or WITH, it opens an indented block with its own clauses. Anything else — an aggregate, a cast, an IN list — is treated as an expression and kept on the line it started on.