Introducing sqlfmt: an SQL gofmt-style formatter · Dimitri FontaineSkip to content<br>Formatting SQL tends to bring some of the same questions again and again:<br>should we uppercase clause keywords? should we put the separating comma at<br>the start of a line to ease refactoring? how to align the SQL clauses with<br>one-another?<br>Over the years I have grown my own SQL style and didn’t find tooling that<br>would implement it. Also, I’ve been asked here and there if there is a tool<br>that would replicate The Art of PostgreSQL<br>SQL indentation style… and now there is finally a good answer to that<br>question!<br>sqlfmt is a gofmt-style formatter<br>that implements my own favorite SQL indentation style. One opinionated<br>style, no configuration knobs. Run it, commit the result, move on.<br>Contents
The style<br>The formatting convention comes from The Art of<br>PostgreSQL — specifically, from the<br>hand-formatted query corpus that runs through the book’s several hundred<br>worked examples.<br>The defining characteristic is river alignment : at each query nesting<br>level, every clause keyword (select, from, where, group by,<br>having, order by) is right-padded to the same column, so the keywords<br>form a vertical river and the expressions that follow them flow naturally to<br>the right.<br>Take a flat, unformatted query:<br>select status, count(*) from results join races using(raceid) where date >= :season group by status having count(*) >= 10 order by count(*) desc;
After sqlfmt:<br>select status, count(*)<br>from results<br>join races using(raceid)<br>where date >= :season<br>group by status<br>having count(*) >= 10<br>order by count(*) desc;
Every keyword above ends at the same column. group by and order by are<br>eight characters — longer than select’s six — so they sit flush-left at<br>base indent. That is a side effect of the alignment rule, not a separate<br>exception for those keywords.<br>The full rule set is documented in<br>STYLE.md in the<br>repository, reverse-engineered from the 343 hand-formatted .sql files in<br>the book’s own query corpus. A few highlights:<br>All SQL keywords and function names are lowercase — count(*),<br>coalesce(...), row_number() over(...).<br>Trailing commas on column lists, one column per line after the first.<br>and/or at the start of continuation lines, right-aligned to end at<br>the same column as where.<br>Columns in CREATE TABLE are left-padded so every data type starts in<br>the same column.<br>Comments are never discarded — leading comments are reindented and<br>reflowed to 78 columns; trailing comments in a block are padded to a<br>shared column.<br>That style is known to best fit SQL queries maintained in their own .sql<br>files rather than integrated in another source code file as a string.<br>If you’re not using an ORM but also maintaining hand-written SQL as a set of<br>static strings within another programming language’s source code, it might<br>be time to see how to manage SQL queries in their own files.<br>Try it now — no install required<br>▸The PostgreSQL SQL Formatter<br>runs sqlfmt directly in your browser via WebAssembly — paste a query, press<br>⌘ / Ctrl + Enter , and get it back in the river style. Free, no signup.<br>The web tool runs the exact same Go engine as the CLI, compiled to<br>WebAssembly with TinyGo. The compressed payload is roughly 130 KB — a better<br>fit for a web page integration compared to the 2.9 MB a standard Go WASM<br>build would produce.<br>CLI usage<br>The interface follows gofmt exactly:<br>sqlfmt query.sql # print formatted output to stdout<br>sqlfmt -w query.sql # rewrite the file in place<br>sqlfmt -l queries/**/*.sql # list files whose formatting would change<br>sqlfmt -d query.sql # show a unified diff instead of full output<br>cat query.sql | sqlfmt # stdin → stdout, pipeable
The -l flag is useful in CI: exit code 1 if any file would change, so a<br>sqlfmt -l $(git diff --name-only '*.sql') step enforces style on every pull<br>request without storing the formatted output in the pipeline.<br>Install with:<br>go install github.com/dimitri/sqlfmt/cmd/sqlfmt@latest
Editor integration<br>Emacs<br>Drop sqlfmt.el on your load path and add a hook:<br>(add-to-list 'load-path "~/dev/sqlfmt/editors/emacs")<br>(add-hook 'sql-mode-hook #'sqlfmt-mode)
With sqlfmt-mode active, C-M-h selects the statement at point and TAB<br>reformats it. sqlfmt-before-save-hook can be used for format-on-save.<br>Vim / Neovim<br>The plugin wires sqlfmt into Vim’s formatprg/equalprg, so the usual<br>motion operators work:<br>gqip " reformat the paragraph under the cursor<br>gg=G " reformat the whole buffer<br>:%!sqlfmt
Why a tokenizer, not an AST<br>Most production SQL formatters — pg_format, sqlfluff, prettier-plugin-sql<br>— are built on token streams rather than parse trees, and sqlfmt follows the<br>same approach. Two reasons matter in practice:<br>Comments. PostgreSQL’s own parser discards comments; any AST-based<br>formatter needs a separate comment-recovery pass. At that point most of the<br>advantage of “let the parser handle structure” is already gone.<br>Robustness. The web widget needs to handle whatever a visitor pastes...