SQLite Formatter

Paste SQL from an app, a migration or the sqlite3 shell, select SQLite and press Ctrl/Cmd+Enter. Placeholders and every identifier quote SQLite accepts come through unchanged.

Input

Settings

History

Load from URL

Quoting and parameters in SQLite

SQLite is permissive about quoting because it tries to run SQL written for other engines. The SQLite dialect accepts the same set:

  • Identifiers can be wrapped in double quotes, backticks or square brackets, so "order", [customer name] and a backtick-quoted group column all format as names.
  • Strings use single quotes, and a quote inside is doubled: 'it''s'. Backslash is not an escape character.
  • Comments are -- and /* */. A MySQL-style # comment is an error.
  • Blob literals such as x'DEADBEEF' are one token.

Bound parameters come in five spellings, and all of them survive formatting: ?, numbered ?2, :since, @region and $channel. This is where the dialect choice shows. Formatted as MySQL, ?2 is split into ? 2; under PostgreSQL, @region turns into @ region. Both would break the binding in your driver, so keep SQLite selected for code from Python’s sqlite3 module, Android Room, better-sqlite3 or Go’s database/sql.

UPSERT, RETURNING and table options

INSERT … ON CONFLICT(sku) DO UPDATE SET … puts the conflict target on the line after VALUES, with the SET list and any trailing WHERE indented beneath it; excluded.qty is left as a plain column reference. RETURNING, available since SQLite 3.35, becomes a clause of its own. INSERT OR REPLACE and REPLACE INTO are recognised as insert forms.

In CREATE TABLE, each column goes on its own line with INTEGER PRIMARY KEY AUTOINCREMENT, CHECK (…), REFERENCES … ON DELETE CASCADE and default expressions kept together. The table options STRICT and WITHOUT ROWID stay after the closing parenthesis. Pasting the output of the shell’s .schema command is a quick way to get a readable schema.

PRAGMA, triggers and other statements

PRAGMA foreign_keys = ON; and PRAGMA table_info(orders); each stay on one line, as do VACUUM, ANALYZE, REINDEX and ATTACH DATABASE 'archive.db' AS archive. Set Blank lines between statements to 0 to keep a block of pragmas compact at the top of a script.

Triggers are only partly understood. In CREATE TRIGGER … AFTER UPDATE ON orders FOR EACH ROW BEGIN … END, the statements in the body are formatted, but the UPDATE event moves to the next line. Window frames have a similar wrinkle: in ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW the AND is treated like a boolean operator and starts a new line. Both results are still valid SQLite.

Things to watch

A column called key is upper-cased to KEY, which SQLite does not care about but a reviewer might. Use Keyword case: As written or quote the name if you want it left alone. Commands that start with a dot, such as .mode csv or .headers on, belong to the sqlite3 shell and must be removed first. The formatter works on text only: it never opens a database file, and the text you paste is processed in the browser without being uploaded. For converting a spreadsheet into INSERT statements, try CSV to SQL.

Examples

Stock upsert with a conditional update

ON CONFLICT … DO UPDATE with the excluded pseudo-table and an upsert WHERE clause.

Input
insert into inventory (sku, qty) values ('SKU-1001', 5) on conflict(sku) do update set qty = qty + excluded.qty, updated_at = datetime('now') where excluded.qty > 0;
Output
INSERT INTO
  inventory (sku, qty)
VALUES
  ('SKU-1001', 5)
ON CONFLICT (sku) DO UPDATE
SET
  qty = qty + excluded.qty,
  updated_at = datetime('now')
WHERE
  excluded.qty > 0;
Open this example in the tool

Orders table with AUTOINCREMENT and STRICT

A typical app schema: foreign key, CHECK constraint, expression default and the STRICT table option.

Input
create table if not exists orders (id integer primary key autoincrement, customer_id integer not null references customers(id) on delete cascade, total real not null check (total >= 0), status text not null default 'pending', created_at text not null default (datetime('now'))) strict;
Output
CREATE TABLE IF NOT EXISTS orders (
  id INTEGER PRIMARY KEY AUTOINCREMENT,
  customer_id INTEGER NOT NULL REFERENCES customers (id) ON DELETE CASCADE,
  total REAL NOT NULL CHECK (total >= 0),
  status TEXT NOT NULL DEFAULT 'pending',
  created_at TEXT NOT NULL DEFAULT (datetime('now'))
) strict;
Open this example in the tool

Monthly revenue with named and numbered parameters

strftime grouping, the JSON ->> operator and both :since and ?2 placeholders, written with lower-case keywords.

Input
select strftime('%Y-%m', o.created_at) as month, count(*) as orders, round(sum(o.total), 2) as revenue, o.payload ->> '$.channel' as channel from orders o where o.created_at >= :since and o.customer_id = ?2 group by month, channel order by month;
Output
select
  strftime('%Y-%m', o.created_at) as month,
  count(*) as orders,
  round(sum(o.total), 2) as revenue,
  o.payload ->> '$.channel' as channel
from
  orders o
where
  o.created_at >= :since
  and o.customer_id = ?2
group by
  month,
  channel
order by
  month;
Open this example in the tool

Connection setup script

Pragmas kept one per line with no blank lines, followed by an INSERT … RETURNING.

Input
pragma foreign_keys = on;
pragma journal_mode = wal;
insert into customers (email, name) values ('ana@example.com', 'Ana') returning id, created_at;
Output
PRAGMA foreign_keys = ON;
PRAGMA journal_mode = wal;
INSERT INTO
  customers (email, name)
VALUES
  ('ana@example.com', 'Ana')
RETURNING
  id,
  created_at;
Open this example in the tool

Common errors and how to fix them

ErrorCauseFix
"#" is not valid SQLite syntaxA # comment was copied from a MySQL script; SQLite only has – and /* */ comments.Change the # at the start of the comment to --.
Unexpected "." hereThe input starts with a sqlite3 shell dot-command such as .mode csv or .headers on.Delete the dot-command lines, format the SQL, and keep the shell commands in a separate part of the script.
This string is never closed — the ' has no matching '
Explained
A quote inside a string was escaped with a backslash (‘O'Brien’), which SQLite treats as a normal character.Write the quote twice: ‘O’‘Brien’.
The query ends before it is completeThe statement was cut off, for example a CASE expression without END or a WHERE with no condition after it.Finish the expression or clause at the end of the input, then format again.
This '(' is never closed
Explained
A CHECK constraint, default expression or function call in the statement lacks its closing parenthesis.Add the matching ) for the bracket at the reported position.

Frequently asked questions

Which placeholder styles does the SQLite formatter keep?

All of them: ?, ?NNN, :name, @name and $name. They are left exactly as written, with no space inserted.

Does it support SQLite UPSERT and RETURNING?

Yes. ON CONFLICT … DO UPDATE / DO NOTHING and RETURNING are recognised clauses, along with INSERT OR REPLACE.

Can it open my .db or .sqlite file?

No. It formats SQL text that you paste or drop in. To get the schema out of a database, run .schema in the sqlite3 shell and paste the result.

Can I format SQL that DB Browser for SQLite or an ORM generated?

Yes. Generated SQL often arrives as one long line with quoted identifiers, which the SQLite dialect lays out without removing the quotes.

Why does my trigger look odd after formatting?

Trigger headers are not fully understood, so the event keyword lands on a new line. The body statements are formatted normally and the trigger still runs.

Related tools