CLI Reference
Synopsis
dfmt [options] [<file-or-dir>...] format and print
dfmt - format stdin to stdout
dfmt write|check|diff [<file-or-dir>...]
dfmt format [<file-or-dir>...] print, said explicitly
dfmt init [<dialect>] create dlab.json
dfmt tune [<sample.sql>] interactive config playground
dfmt completion <bash|zsh|fish> print a completion script
The verbs
What dfmt does with the formatted result is a verb, not a flag. Exactly one applies, and because they are verbs you cannot ask for two.
| Command | What it does |
|---|---|
dfmt <file> | Print the formatted file to stdout. Writes nothing. |
dfmt / dfmt <dir> | Report what would change, and name the verb that does it. Writes nothing, prints no SQL — there is no useful way to print a directory's worth of formatted files at you. |
dfmt format … | The same thing, said explicitly. |
dfmt diff … | Print a unified diff of what would change. Writes nothing. |
dfmt write … | Write formatted output back, only for files that actually change. |
dfmt check … | Write nothing, print what needs formatting, exit 1 if anything does. |
Printing is the bare form, so an accidental dfmt . cannot damage a repository.
All of them take the same options — dialect, file selection, and every formatting option — and it does not matter which side of the verb you put them on:
dfmt write --width 100 .
dfmt --width 100 write .
When the target is a single file and the verb is the bare/format one, stdout carries only the
formatted SQL — no summary, no filename — so it pipes and redirects cleanly. - reads stdin and
does the same.
A directory or file named like a verb needs a path that looks like one: dfmt check is the command,
dfmt ./check is the directory.
dfmt diff on a terminal
Two things happen when stdout is a terminal, and neither happens when it is a pipe:
- The output goes through a pager.
$DFMT_PAGER, else$PAGER, elseless. Set either tocatto turn paging off. A 1,200-model project produces well over a hundred thousand lines of diff; without a pager the beginning is gone before you can read it.qends it, and so doesCtrl-C— dfmt stops formatting the moment the reader leaves rather than working through the rest of the project for nobody. - The diff is written for a person: the path once as a heading with its own
+n −ntally, a rule beneath it, dim hunk headers, red removals, green additions, and a closing summary. - Changed lines are paired and marked. A formatter rarely rewrites a line — it moves a comma or changes an indent. Where a run of removals matches the run of additions that replaced it, the two are interleaved so each line sits beside its replacement, and only the words that actually differ are highlighted. Lines that are not versions of each other stay in block form rather than being paired up into a correspondence that is not there.
Both are presentation. The diff itself is the same one either way: piped, you get the plain form
that git apply takes, with removals and additions in blocks and no highlighting.
Redirected or piped, you get the plain ---/+++/@@ form with no escape sequences — the one
git apply takes. --color never gives you that form on a terminal too.
Every command answers --help for itself: dfmt write --help lists what write takes, not what
dfmt takes.
--write, --check and --diff still work, so existing scripts and CI pipelines keep running.
They are no longer advertised, and they are the only spelling that can conflict: dfmt --write --diff is a usage error, while dfmt write diff simply cannot be said.
Where the dialect comes from
--dialect > dlab.json "dialect" > dbt adapter type > error
There is no default. An ansi fallback used to sit at the end of that chain and it was removed:
the parsers recover from anything, so formatting Snowflake SQL as ANSI does not fail, it just
quietly produces a worse — occasionally wrong — result. Being told to pass --dialect is better
than being handed a mangled file.
There is also no dialect probing. Parsing under several dialects and keeping the best fit is the same error one level up, and the grammars overlap enough that candidates routinely tie at zero errors anyway.
Where the language comes from
If a file contains Jinja markers, dfmt needs a dbt project to evaluate them against — it looks for
dbt_project.yml walking up from the file, not from your shell's working directory. If there
isn't one, the file is refused: left untouched, reported as a warning, and counted by check
as not assessed.
That refusal is the point. A template handed to a SQL parser does not fail — {% if x %} lexes as
operators and identifiers, and the result is a clean parse of something you never wrote. Use
--no-jinja if you genuinely want the tags lexed as SQL.
Options
Selecting files
| Flag | Description |
|---|---|
<file-or-dir>... | What to format. With dlab.json, filtered by its include/exclude; without one, every .sql file. Either way, vendored directories (dbt_packages, target, node_modules, …) are skipped unless include names one. |
- | Read from stdin, write to stdout. |
--model <name> | Format one dbt model by name. |
--changed-since <ref> | Only .sql files changed since a git ref. |
-d, --directory <dir> | Run as if started in <dir>. Accepted by every command that runs somewhere, on either side of the verb: dfmt -d ~/proj diff . and dfmt diff -d ~/proj . are the same. |
Behaviour
| Flag | Description |
|---|---|
--dialect <name> | Set the SQL dialect. Required when there is no dlab.json. |
--no-jinja | Treat the file as plain SQL even inside a dbt project. |
-v, --verbose | Also report warnings and files that did not change. |
--color <when> | auto (default), always, never. |
--version | Print the version. |
-h, --help | Print help. |
Formatting
Every option in dlab.json is also a flag, and flags win. --keyword-case upper,
--max-line-width 100, --preserve-breaks breaks_only, and so on — see the
Configuration Reference for the full list with defaults and examples,
or dfmt --help.
Five shorthands exist for the common ones:
| Flag | Same as |
|---|---|
--uppercase | --keyword-case upper |
--lowercase | --keyword-case lower |
--leading-commas | --comma-style leading |
--indent N | --indent-width N |
--width N | --max-line-width N |
Boolean options accept --flag, --flag true, or --flag false.
Commands
dfmt init [<dialect>]
Writes dlab.json in the current directory (or -d <dir>), recording the dialect and default
include patterns. Refuses to overwrite an existing config. --all writes every formatting option
explicitly instead of leaving them at their defaults.
dfmt tune [<sample.sql>]
An interactive playground for the formatting options: pick a file or dbt model on the left, change
options on the right, watch the sample re-format as you go. Choices are steppers; ranges like
maxLineWidth are sliders you can click or drag. Needs a real terminal.
w writes your choices to the project's dlab.json — the one dfmt reads, found by walking up, not
a new file in the current directory. It edits the "dfmt" section and leaves the rest of the
document alone, so the dialect, the include/exclude patterns and any other tool's settings survive.
dfmt completion <shell>
Prints a completion script for bash, zsh, or fish to stdout. The bash script works under zsh
via bashcompinit.
Configuration precedence
dialect defaults → dlab.json "dfmt" → command-line flags
Keys under "dfmt" that are not formatter options are rejected when the file loads, with a
suggestion if one is close. An option that was removed says so, and says what replaced it.
Exit codes
| Code | Meaning |
|---|---|
| 0 | Success, or check found nothing to do. |
| 1 | check found files needing formatting, or a run failed (bad config, unknown dialect, unreadable file). |
| 2 | Usage error — conflicting modes, unparseable arguments. |
Dialects
--dialect takes any of:
ansi athena bigquery clickhouse cockroachdb databricks
db2 doris duckdb dune fabric hive
impala mariadb motherduck mysql oracle planetscale
postgres presto redshift singlestore snowflake spanner
spark sqlite starburst starrocks synapse tidb
trino tsql yugabyte
Managed and re-badged engines are accepted as names of their own and resolve to the grammar they actually are:
| Name | Formats as |
|---|---|
timescaledb, citus, neon, supabase, alloydb, aurorapostgres, spannerpg | postgres |
auroramysql | mysql |
azuresql | tsql |
chdb | clickhouse |
Passing a name dfmt does not know prints the accepted list, so the CLI is the authority if this page ever drifts.