Format SQL and Pretty-Print Queries Online
Turn a compact or inconsistently styled SQL query into a readable version with uppercase keywords, predictable spacing, and structured indentation.
Run — free
The formatter performs a deterministic lexical pass, so it preserves quoted strings and identifiers while making clauses and nested expressions easier to scan. It also checks the structure before producing output: unmatched parentheses, unfinished quoted values, and unterminated quoted identifiers are rejected instead of being disguised by attractive formatting. Use it interactively in the browser or automate the same behavior through the API for $0.002 per request.
What the formatter changes and preserves
The formatter normalizes recognized SQL keywords to uppercase and separates major clauses such as SELECT, FROM, WHERE, GROUP BY, ORDER BY, HAVING, LIMIT, and common joins onto readable lines. Commas divide lists into one item per line, while parentheses create an additional indentation level for nested expressions and subqueries. Operators receive consistent surrounding spaces. At the same time, the formatter treats quoted material as protected content. Text inside single quotes, quoted identifiers inside double quotes or backticks, and bracketed identifiers retain their original characters and casing. Line comments and block comments are also kept in the output. This approach makes a query easier to review without pretending to understand a particular database schema. Formatting is intentionally deterministic: identical input produces identical output, with no network call, model interpretation, random choice, or environment-dependent behavior. The result is returned in the formatted_sql field as plain text that can be copied into an editor, migration, report, or code review.
How structural validation protects the result
Pretty output is not useful when it conceals an incomplete query, so validation happens during tokenization rather than after layout. Every opening parenthesis must have a corresponding closing parenthesis, and a closing parenthesis cannot appear before its opening partner. Single-quoted strings, double-quoted identifiers, backtick identifiers, and bracketed identifiers must all terminate correctly. Doubled quote characters inside a quoted value are recognized as escapes, allowing common SQL literals such as an apostrophe represented by two single quotes. Backslash escapes are also kept together where they occur. An unterminated block comment is rejected because its remainder cannot be formatted safely. These checks are structural rather than semantic: the tool does not connect to a database, verify table or column names, select a SQL dialect, or determine whether the statement will execute. A balanced query may still contain a database-specific syntax error. The error response is therefore focused and honest, identifying unbalanced parentheses or quotes as invalid input without claiming full SQL compilation.
Using formatted SQL in a reliable workflow
Paste the complete query into the query field and submit it as a string. For interactive work, compare the formatted result with the original before replacing production code, especially when the statement uses vendor-specific operators or unusual quoting rules. In automation, run the formatter before storing generated migrations, attaching SQL to review tickets, or comparing query changes. Consistent layout reduces noisy diffs: reviewers can focus on joins, predicates, selected columns, and ordering instead of arguing about capitalization and spaces. Because comments and quoted values are retained, the result is suitable for documentation as well as execution, but formatting should never replace database validation or tests. The browser tool runs the same pure formatting function as the API, which keeps results aligned across manual and automated use. The API price is $0.002 per request. Inputs are processed without external services, and the operation has no reason to retain a query after the response. If validation fails, fix the unmatched delimiter in the source and submit the complete statement again.
What you can do with it
Clean up generated SQL
Convert a single-line query from an ORM, log, or reporting tool into a layout that is easier to inspect and discuss.
Standardize migration reviews
Apply predictable keyword casing and indentation before a migration enters code review, reducing formatting-only differences.
Catch incomplete pasted queries
Reject SQL copied with a missing quote or parenthesis before publishing it in documentation or passing it to another system.
FAQ
Does the formatter execute my SQL?
No. It performs local lexical formatting and structural checks only. It does not connect to a database or inspect a schema.
Which keyword casing does it use?
Recognized SQL keywords are converted to uppercase. Quoted text and identifiers preserve their original casing.
What validation does it perform?
It rejects unmatched parentheses, unterminated single or double quotes, unfinished backtick or bracketed identifiers, and unterminated block comments.
Does it support every SQL dialect?
It formats common SQL syntax without selecting a dialect. Vendor-specific constructs are preserved as tokens, but the tool does not certify their semantics.
How much does the API cost?
The API costs $0.002 per request. The browser version runs the same deterministic formatting logic.
For developers — API access
Everything on this page is available programmatically. This section is for teams who want to wire it into their own systems; everyone else can just use the tool above.
API endpoint
Prefer to automate it? One authenticated POST creates the task; the result comes back by webhook or a signed link. The same capability also runs here on the web, by email and from Telegram — and soon from our app too.
Call it from your stack
curl -X POST https://api.kit.forhosting.com/web/sql-format \
-H "Authorization: Bearer $KIT_KEY" \
-H "Content-Type: application/json" \
-d '{"query":"select u.id,u.name,count(o.id) as orders from users u left join orders o on o.user_id=u.id where u.active=true group by u.id,u.name order by orders desc;"}'const res = await fetch("https://api.kit.forhosting.com/web/sql-format", {
method: "POST",
headers: {
"Authorization": `Bearer ${process.env.KIT_KEY}`,
"Content-Type": "application/json"
},
body: JSON.stringify({
"query": "select u.id,u.name,count(o.id) as orders from users u left join orders o on o.user_id=u.id where u.active=true group by u.id,u.name order by orders desc;"
})
});
const { task_id } = await res.json();import os, requests
res = requests.post(
"https://api.kit.forhosting.com/web/sql-format",
headers={"Authorization": f"Bearer {os.environ['KIT_KEY']}"},
json={
"query": "select u.id,u.name,count(o.id) as orders from users u left join orders o on o.user_id=u.id where u.active=true group by u.id,u.name order by orders desc;"
},
)
task_id = res.json()["task_id"]<?php
$res = file_get_contents("https://api.kit.forhosting.com/web/sql-format", false, stream_context_create([
"http" => [
"method" => "POST",
"header" => "Authorization: Bearer " . getenv("KIT_KEY") . "\r\nContent-Type: application/json",
"content" => '{"query":"select u.id,u.name,count(o.id) as orders from users u left join orders o on o.user_id=u.id where u.active=true group by u.id,u.name order by orders desc;"}',
],
]));
$task = json_decode($res, true);body := bytes.NewBufferString(`{"query":"select u.id,u.name,count(o.id) as orders from users u left join orders o on o.user_id=u.id where u.active=true group by u.id,u.name order by orders desc;"}`)
req, _ := http.NewRequest("POST", "https://api.kit.forhosting.com/web/sql-format", body)
req.Header.Set("Authorization", "Bearer "+os.Getenv("KIT_KEY"))
req.Header.Set("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)Example request
{
"query": "select u.id,u.name,count(o.id) as orders from users u left join orders o on o.user_id=u.id where u.active=true group by u.id,u.name order by orders desc;"
}Example response
{
"task_id": "tsk_a1b2c3d4e5f6a1b2c3d4e5f6",
"type": "web.sql_format",
"status": "queued",
"_links": {
"result": "/tasks/tsk_…/result"
}
}The API is asynchronous: the call returns a task_id immediately and the result arrives by webhook. Polling is capped at 1 req/s per task.
Pricing
Published price — no tokens, no invented credits. A failed task is never charged.
Limits
timeout_sec | 30 |
max_crawl_pages | 25 |
Errors
| HTTP | Code | Meaning |
|---|---|---|
401 | unauthorized | Missing or invalid API key. |
402 | insufficient_balance | Your balance doesn't cover the task price. |
404 | unknown_type | That task type doesn't exist. |
429 | rate_limited | Too many requests. Use the webhook instead of polling. |