How to use
- Paste a query, drop a
.sqlfile on the box, or press Load sample. - Pick your database under Dialect, so quotes, comments and backslashes are read the way your server reads them.
- Choose Format for one clause per line, or Minify for one line per statement.
- Read the box under the result: it lists any unclosed quote, unmatched bracket or stray comma, with its line and column.
- Copy to clipboard or Download .sql.
What the formatter changes, and what it never touches
A query copied out of a log, an ORM's debug output or a string in application code usually arrives as one long line. The SQL Formatter lays it out the way most teams write SQL by hand: each clause (SELECT, FROM, WHERE, GROUP BY, ORDER BY) starts a line with its items indented underneath, joins sit under FROM with their ON condition, and every AND or OR in a condition starts a new line, except the AND that belongs to BETWEEN.
Subqueries and the bodies of WITH clauses are indented one level. A bracketed group that fits in 60 characters stays on one line, so COUNT(*) or a short window like OVER (PARTITION BY customer_id) never gets spread over five lines. IN lists and VALUES rows stay on one line at any length, one row per line, and a CREATE TABLE column list always gets one column per line.
Only whitespace and keyword case change. Names, string values, numbers and comments are written back exactly as typed, including the body of a PostgreSQL function between $$ markers. The tool is not a database and not a full parser: it never runs anything, and its warnings cover the mistakes that break a statement in every dialect rather than your schema.
Example
The query that Load sample inserts, as one line:
select o.id, c.name as customer, sum(oi.quantity * oi.unit_price) as total, case when o.status = 'shipped' then 'done' else 'open' end as state from orders o join customers c on c.id = o.customer_id left join order_items oi on oi.order_id = o.id where o.created_at >= '2026-01-01' and c.country in ('US', 'CA') group by o.id, c.name, o.status having sum(oi.quantity) > 2 order by total desc limit 20;comes out with the default options (Standard SQL, UPPERCASE, 2 spaces, commas at the end of the line) as:
SELECT
o.id,
c.name AS customer,
SUM(oi.quantity * oi.unit_price) AS total,
CASE
WHEN o.status = 'shipped' THEN 'done'
ELSE 'open'
END AS state
FROM
orders o
JOIN customers c ON c.id = o.customer_id
LEFT JOIN order_items oi ON oi.order_id = o.id
WHERE
o.created_at >= '2026-01-01'
AND c.country IN ('US', 'CA')
GROUP BY
o.id,
c.name,
o.status
HAVING
SUM(oi.quantity) > 2
ORDER BY
total DESC
LIMIT 20;The two joins sit under FROM, the CASE expression gets its own block with one WHEN per line, and the IN list and the SUM calls stay on one line because they fit.
Options explained
- Format / Minify: Format lays the query out as described above. Minify writes each statement on one line with the fewest spaces that keep it valid, and removes comments except MySQL's
/*! … */executable comments and/*+ … */optimizer hints, which change what a query does. - Dialect (the Database list) decides how the text is read, not how it is laid out. See the table below.
- Keyword case: UPPERCASE / lowercase / As typed applies to keywords, built-in functions and data types. As typed keeps each keyword the way you wrote it and only changes spacing.
- Indent: 2 spaces / 4 spaces / Tab is the width of one indentation level. Format only.
- Commas: End of line / Start of line. Start of line puts each comma in front of the next item, a style many people use so that commenting out the last column never leaves a dangling comma. Format only.
- No-wrap (under the result) stops long lines from wrapping, with a horizontal scroll bar instead.
With Start of line commas, select id, email, created_at from users becomes:
SELECT
id
, email
, created_at
FROM
users| Dialect | Strings | Quoted names | Comments |
|---|---|---|---|
| Standard SQL | '…', with '' for a quote inside | "…" | --, /* */ |
| MySQL / MariaDB | '…' and "…", backslash escapes | `…` | -- followed by a space, #, /* */ |
| PostgreSQL | '…', E'…' with backslash escapes, $$…$$ | "…" | --, nested /* */ |
| SQL Server (T-SQL) | '…', N'…' | […], "…" | --, nested /* */; GO alone on a line separates batches |
| SQLite | '…' | "…", `…`, […] | --, /* */ |
| BigQuery | '…', "…", triple-quoted, r'…' raw | `…` | --, #, /* */ |
Edge cases and common errors
- Two dashes mean different things in MySQL. MySQL starts a comment only when whitespace follows
--, sobalance--1is arithmetic there and comes out asbalance - -1. In Standard SQL the same text starts a comment, and the formatter keeps it as one:balance --1 from account. Pick the dialect you actually run. - A space before a function's bracket breaks MySQL. By default MySQL rejects
COUNT (*)for its built-in functions, so the formatter never puts a space there and removes one it finds:count (*)becomesCOUNT(*). Names it does not know, such as your own functions, keep the spacing you gave them. - A comma before FROM, usually left behind after deleting the last column, is reported as "Line 1, column 16: Comma right before FROM: remove the comma after the last item." BigQuery accepts a trailing comma at the end of the SELECT list, so with that dialect the warning does not appear.
- An unclosed bracket is reported where it opens: "Line 1, column 8: This ( is never closed." The query is still formatted, so you can see where the layout goes wrong.
- Backslash escapes in the wrong dialect make the rest of the query look like one long string. The FAQ below has the message and the fix.
- Column names that look like keywords are left alone:
select date, count, year(created_at) from eventskeepsdateandcountas typed and only uppercasesYEAR(created_at), because there it is called as a function. - Invisible non-breaking spaces, which arrive with text copied from web pages, chat apps and documents, look like spaces but database parsers do not treat them as one, so an innocent-looking query fails with a syntax error. The formatter reads them as whitespace and writes an ordinary space in their place.
- Minify and comments:
select /*+ BKA(o) */ id, -- the key total from orders o;minifies toSELECT /*+ BKA(o) */ id,total FROM orders o;. The hint stays because MySQL acts on it. The line comment has to go: on a single line it would comment out everything after it. - Stored procedures and scripts (BEGIN … END blocks, IF, loops, DECLARE) are laid out as a sequence of plain statements. Nothing is lost, but the result reads better for queries and table definitions than for procedural code. A SQL Server column or alias called
gois never left alone on a line, where SSMS would read it as a batch separator. - Large files: a 200 KB script of about 1,250 statements is formatted and drawn in about a fifth of a second on a laptop. Above 50,000 characters the tool waits for a pause in typing before it reformats, so each keystroke stays instant while you edit.
Do the same thing in code
import sqlparse
print(sqlparse.format(sql, reindent=True, keyword_case="upper"))import { format } from 'sql-formatter';
console.log(format(sql, { language: 'postgresql', keywordCase: 'upper', tabWidth: 2 }));Python's sqlparse and the sql-formatter package for JavaScript both have options along the same lines as this page, but each has its own layout rules, so the output will not match line for line. To compare two versions of a query, format both here with the same options first and then paste them into the Text Diff Checker, so only real changes show up.
Building a WHERE id IN (…) list from a column of values is a different job: the SQL IN preset of Column to Comma Separated List does it and doubles any quote inside a value. A JSON column you pulled out of a result set is easier to read in the JSON Formatter.