# sqlfmt formats dbt SQL the way black formats Python, and it refuses to be configured

> sqlfmt is a Python command line formatter for dbt SQL that works on Jinja-templated files, is fast enough to run in a pre-commit hook or CI, and enforces one style with a single configurable value. It lexes rather than parses, which is what makes it quick and what limits it to a handful of statement types.

**tconbeer/sqlfmt** — sqlfmt formats your dbt SQL files so you don't have to

- Repository: https://github.com/tconbeer/sqlfmt
- Website: https://sqlfmt.com
- Stars: 551 · Forks: 38
- Language: Python
- License: NOASSERTION
- Published: 2026-10-22 · Updated: 2026-10-22 · Language: en
- Canonical page: https://hysenlabs.com/projects/tconbeer-sqlfmt

## The four arguments, and the one design decision behind them

sqlfmt is described as formatting your dbt SQL files so you don't have to, and it compares itself to black, gofmt and rustfmt, but for SQL. The comparison is the right one, because those are all formatters that impose a style rather than check one.

The four reasons the README gives are worth reading in full, because each one is a different kind of argument.

The first is that sqlfmt promotes collaboration: an auto-formatter makes it easier to collaborate with your team and solicit contributions from new people, and you will never have to mention or argue about code style in code reviews again. That is a workflow argument rather than a technical one, and for a project with external contributors it is the argument that matters most.

The second is speed: forget about formatting your code and spend your time on business logic instead, because sqlfmt processes hundreds of files per second and only operates on files that have changed since the last run. That last clause is what makes it viable in a pre-commit hook or a CI job on a large repository.

The third is Jinja: it formats the code that users look at, and therefore doesn't need to know anything about what happens after the templates are rendered. That sentence is the key to the whole design and is worth coming back to below.

The fourth is workflow integration: as a CLI written in Python, it is easy to install locally on any OS and run in CI, it plays well with dbt, pre-commit, SQLFluff, VSCode and GitHub Actions, and it powers the dbt Cloud IDE's Format button.

That last claim is the strongest evidence about the tool's maturity. A format button in a commercial dbt Cloud IDE means the format is settled enough that a hosted product can promise a button will not produce a diff, which is the thing a formatter has to get right before it can be automatic.

The primary language is Python, the licence field in pyproject.toml is Apache-2.0 while the licence field on the repository is a custom one GitHub cannot classify, and the default branch is main. The recent releases are v0.32.0 from 2026-08-10, v0.31.0 from 2026-08-03 and v0.30.0 from 2026-05-04, and the last push was on 2026-10-19.

## One style, no configuration, and a deliberate refusal to parse

Two sentences in the README define the boundaries of the tool, and both are limits rather than features.

The first: sqlfmt is not configurable, except for line length. It enforces a single style. It maintains comments and some extra newlines, but largely ignores all indentation and line breaks in the input file.

So unlike a linter with a hundred rules, there is no settings file for style, no way to say that your team wants a different keyword casing or a different indent, and one knob for line length. That is a deliberate position, and it is what makes the tool usable as a shared standard rather than a source of configuration drift. A formatter where every team can configure it ends up formatting differently in every repository, which defeats the purpose.

The second sentence is more interesting technically: sqlfmt is not a linter. It does not parse your code into an AST, it just lexes it and tracks a small subset of tokens that impact formatting. This lets us do one thing and do it well: sqlfmt is very fast, and easier to maintain and extend than linters that need a full SQL grammar.

That is the trade, stated plainly. A full SQL grammar would let sqlfmt understand every statement, format complex constructs correctly and tell you about problems. Instead it lexes, keeps track of the tokens that affect layout, and formats the shapes it recognises. The result is a formatter that is quick, small and predictable, at the cost of not understanding much.

The consequence for coverage is stated next: for now, sqlfmt only works on select, delete, grant, revoke and create function statements, which is all you need if you use sqlfmt with a dbt project. It is being extended to additional DDL and DML, with a tracking issue linked for more information.

That list is not a limitation for the dbt use case, because dbt models are select statements. It is a real limitation for anyone who also wants their migrations, inserts or update statements formatted, and the tracking issue is where you would check whether that has changed since the version you installed.

## Installing it, and the PyPI name that will trip you up

The recommended path is uv, and the reason is spelled out: sqlfmt is a command-line tool built in Python that runs on MacOS, Linux and Windows, it is distributed on PyPI under the name shandy-sqlfmt, and the author strongly recommends using uv.

Install uv first, from a POSIX shell:

```bash
    curl -LsSf https://astral.sh/uv/install.sh | sh
```

Or using Windows Powershell:

```pwsh
    powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/install.ps1 | iex"
```

Then install sqlfmt as a tool using uv:

```bash
    uv tool install "shandy-sqlfmt[jinjafmt]"
```

That command installs sqlfmt into an isolated environment and adds it to your PATH so you can easily run the executable. The note in the docs is that, depending on your shell and OS, you may need single or double quotes around the package name.

Test the installation by running sqlfmt with no arguments:

```bash
    sqlfmt
```

and you should see some ASCII art and help text.

The warning about PyPI names is the part worth memorising: the PyPI distribution is shandy-sqlfmt, NOT sqlfmt, which is a different, unrelated but not malicious package. The README calls this unfortunate and says the author cannot do anything about it. So if you follow a tutorial that says pip install sqlfmt, you will install somebody else's code, and the package name mismatch is the reason the recommended command includes quotes and the extra.

The alternatives are pip or something pip-like, with Python 3.9 or above and an activated virtual environment:

```bash
    pip install "shandy-sqlfmt[jinjafmt]"
```

or Docker, which skips installation altogether by pulling the official image, with the docs on running sqlfmt in a container linked.

There is also a web version. Want to test out sqlfmt on a query before you install it, and you can go to sqlfmt.com to use the interactive, web-based version.

## Running it: format, check, diff and stdin

The command surface is deliberately close to Black's, and the README says so: if you have used the Python code formatter Black, the sqlfmt commands will look familiar.

To list commands and options:

```bash
sqlfmt --help
```

To format all .sql and .sql.jinja files in your current working directory and all nested directories, with the trailing dot denoting the current directory:

```bash
sqlfmt .
```

For CI, the --check and --diff options let you inspect without writing, and sqlfmt will exit with code 1 if the files on disk are not properly formatted:

```bash
sqlfmt --check .
sqlfmt --diff .
```

That exit code is the part that matters for a pipeline. A check mode that fails the build is how a formatter becomes a team standard rather than a suggestion, and the exit code of 1 is what the pre-commit hook and the GitHub Actions integration consume.

It also formats code passed through standard input, by passing - as the files argument, with the formatted code going to stdout and all other output routed to stderr:

```bash
echo "select 1" | sqlfmt -
```

The stdout and stderr split is a detail worth knowing, because it is what lets you pipe sqlfmt output into another command without the help text or the progress bars getting in the way.

Configuration goes in pyproject.toml under a [tool.sqlfmt] section header. Any command-line option can be set there, and options passed on the command line override the settings in the config file.

The Jinja handling has its own extra. sqlfmt loves properly-formatted jinja, too, and the docs cover using the jinjafmt extra or disabling jinja formatting. The extra pulls in Black, since the pyproject lists jinjafmt = ["black>=24"], so a Jinja template is formatted by running Black over it.

The repo carries a .pre-commit-config.yaml and a .pre-commit-hooks.yaml, which is the integration the README mentions, and a Dockerfile whose default command is sqlfmt with the current directory.

## The Jinja argument, and the warning the README leads with

The most interesting claim in the README is that sqlfmt works with Jinja because it formats the code that users look at, and therefore doesn't need to know anything about what happens after the templates are rendered.

Consider what a dbt model actually is. It is a Jinja template with SQL inside it, and the Jinja is what makes the model a dbt model: ref calls, config calls, loops over a list of columns, conditional logic, macros from other files. A tool that wanted to format the rendered SQL would need to execute the template, which needs a database connection, a profile and the whole dbt runtime, and the output would be a file you could not put back into the repository.

sqlfmt takes the other approach. It formats the source, treating the Jinja as opaque content it must preserve and the SQL between it as the thing it lays out. That is why the tool is fast, and it is why it works on a .sql.jinja file with no project context at all.

The cost is that sqlfmt cannot know whether the SQL it formatted is valid in the context it will run in, and the README says as much. The Before You Begin block is a danger callout: sqlfmt may not always produce the formatted output you want, and it might even break your SQL syntax. It is highly recommended to only run sqlfmt on files in a version control system like git, so that it is easy to revert any changes made by sqlfmt, and on your first run you should make a commit before running sqlfmt.

That is an unusually blunt warning for a formatting tool, and it is the right one. A formatter that rearranges text in files you have not committed is a tool that can lose work, and the fact that the author says it outright rather than burying it is a good sign about the rest of the project's behaviour.

The repository also ships a second executable, sqlfmt_primer, registered in the pyproject scripts alongside sqlfmt itself. The Makefile profiling targets run it over three different codebases, all, gitlab and rittman, which suggests the primer formats large real-world projects and the profiles measure the cost of doing so. That is a sensible way to keep a formatter honest about performance, and it explains the claim about processing hundreds of files per second.

## Where sqlfmt is the wrong tool

Three limits, and the first is about the SQL it will touch.

The first is the statement coverage. sqlfmt works on select, delete, grant, revoke and create function, and it is being extended to additional DDL and DML. If your repository contains migrations with create table statements, or plain SQL scripts with inserts, sqlfmt will not format them, and it will not tell you that it skipped them either, since it does not parse. A repository where models are selects and everything else is migration SQL needs SQLFluff or a real parser for the other half.

The second is the single-style position, and it cuts both ways. There is one configuration knob and no per-project settings, which means you cannot get the style your company has standardised on if it differs from sqlfmt's. If your organisation has a house SQL style, sqlfmt will replace it rather than implement it, and that is a conversation to have before adopting it rather than after.

The third is that it does not check anything. Because sqlfmt lexes rather than parses, it cannot tell you that your query references a column that does not exist or that a CTE is unused. If your SQL quality problem is linting rather than formatting, you need SQLFluff, which the README names as a tool sqlfmt plays well with, and the two have different jobs rather than overlapping ones.

There is also the operational caveat to plan for. sqlfmt exits with code 1 on unformatted files, so introducing it to an existing repository means the first CI run fails on every file, and the team has to accept one large formatting commit. Doing that in a single pull request makes the diff unreviewable and the blame history useless, so the usual practice is a dedicated formatting pass on a branch, which the README's own advice about committing before the first run is a precondition for.

The alternative approach is to write the style by hand and enforce it with a linter, which is what SQLFluff is for. The difference in approach is that a linter can be configured rule by rule and can report semantic problems, while sqlfmt guarantees one output and reports nothing. If your problem is that a repository has thirty different formatting styles and new contributors keep starting arguments about it, sqlfmt is the better answer. If your problem is that nobody can tell which of your models is wrong, it is not.

## Conclusion

Adopt sqlfmt if you maintain dbt models and want every SQL file in the project to come out in one style without arguing about formatting in review, because the tool enforces a single style, keeps Jinja intact and only touches files that changed. Do not adopt it if you need a configurable formatter or a linter, since the README is explicit that it is not configurable except for line length and is not a linter, and do not run it over anything outside version control, because the README warns it may not always produce the output you want and might even break your SQL syntax. Verify first that your statements are among the supported types, select, delete, grant, revoke and create function, that you have committed your files before the first run, that the jinjafmt extra is installed if you want your Jinja formatted rather than left alone, and that you install the distribution named shandy-sqlfmt, since the name sqlfmt on PyPI belongs to an unrelated package.

## FAQ

### What is sqlfmt and what does it format?

It is a Python command-line formatter for dbt SQL files, comparable to black, gofmt and rustfmt but for SQL. It enforces a single style, maintains comments and some extra newlines while largely ignoring the input indentation, and it works with Jinja templates so it formats the source users look at rather than the rendered result.

### How do I install sqlfmt?

The recommended route is uv: install uv, then run uv tool install "shandy-sqlfmt[jinjafmt]", then run sqlfmt with no arguments to see the ASCII art and help text. pip or pipx work too with Python 3.9 or above, and there is a Docker image and a web version at sqlfmt.com.

### What is the correct PyPI package name for sqlfmt?

The distribution is shandy-sqlfmt, not sqlfmt. The README warns that sqlfmt on PyPI is a different, unrelated but not malicious package, and says the author cannot do anything about it.

### Which SQL statements does sqlfmt support?

For now it works on select, delete, grant, revoke and create function statements, which the README says is all you need with a dbt project. It is being extended to additional DDL and DML, tracked in issue 262.

### How do I run sqlfmt in CI so it fails on unformatted files?

Use the --check or --diff options, which do not modify the files on disk, and sqlfmt will exit with code 1 if the files are not properly formatted. Command-line options override the settings in a [tool.sqlfmt] section of pyproject.toml.

### Does sqlfmt lint SQL or can it break my queries?

It is not a linter: it does not parse code into an AST, it lexes it and tracks a small subset of tokens that impact formatting. The README warns that sqlfmt may not always produce the output you want and might even break your SQL syntax, and recommends only running it on files in version control.

## Sources

- [Issues](https://github.com/tconbeer/sqlfmt/issues)
- [Project website](https://sqlfmt.com)
- [README](https://github.com/tconbeer/sqlfmt/blob/main/README.md)
- [Releases](https://github.com/tconbeer/sqlfmt/releases)
- [tconbeer/sqlfmt on GitHub](https://github.com/tconbeer/sqlfmt)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/tconbeer-sqlfmt
