SQL Formatter

Lay out SQL with one clause per line and indented subqueries, or minify it to one line. Points out unclosed quotes and stray commas.

0 characters

Or drop a .sql/.txt file onto the box above.

Action
Dialect
Keywords
Layout

How to use

  1. Paste a query, drop a .sql file on the box, or press Load sample.
  2. Pick your database under Dialect, so quotes, comments and backslashes are read the way your server reads them.
  3. Choose Format for one clause per line, or Minify for one line per statement.
  4. Read the box under the result: it lists any unclosed quote, unmatched bracket or stray comma, with its line and column.
  5. 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

With Start of line commas, select id, email, created_at from users becomes:

SELECT
  id
  , email
  , created_at
FROM
  users
DialectStringsQuoted namesComments
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

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.

SQL Formatter FAQ

Does formatting change what my query does?

No. The formatter only moves whitespace and changes the case of keywords it recognises. Table and column names, string values, numbers and comments are written back character for character. The site's tests re-read every formatted result with the same tokenizer and check that it produces the same pieces as the input, in the same order, on hand-written queries and on 18,000 generated ones per run.

Why does it say a string is never closed when the query runs fine?

The Dialect probably does not match your database. MySQL and BigQuery let you write a quote inside a string as \'; Standard SQL, PostgreSQL, SQL Server and SQLite do not, so there the string ends at that quote and the rest of the query looks unclosed. The warning says so: "This string is never closed. The query uses \' inside a string: that is a MySQL/BigQuery escape; pick that dialect, or write '' instead."

Can it tell me whether my SQL is valid?

Only partly. It catches the mistakes that stop a statement in every dialect: an unclosed string, quoted name or comment, brackets that do not match, a comma right before FROM, WHERE or a closing bracket, and two commas in a row. It does not know your tables or columns and does not check grammar, so a misspelled keyword passes. Running the query with EXPLAIN in front is the reliable check.

Why are words like date and count not uppercased?

Because they are often column names. Words that double as common column names (date, year, count, text, json and similar) are recased only when called as a function, like COUNT(*) or YEAR(created_at), or in the type position of a CREATE TABLE column. A name written after a dot, as in o.order, is never treated as a keyword at all.

Is it safe to paste a query that contains customer data?

The page has no server side to send it to. Formatting runs in your browser with JavaScript, the query is never executed, and nothing you paste is stored or uploaded; you can disconnect from the network after the page loads and the tool keeps working. Your browser remembers the options you picked; the query itself is not kept.

Runs entirely in your browser: this page is a static file with no server behind it, so nothing you paste can be uploaded. Last reviewed by Parthesh Gupta.