A wrapper around dprint that adds multi-config support and several missing features.
- Per-file config profiles — select dprint config by file path using glob rules (dprint#996)
- Content-based matching — override profile by file content (e.g. skip generated files)
- Local config overrides — project-level
dprint.jsonthat merges with the matched profile viaextends - Unified diff output —
dprint checkwith real unified diff and optional pager (dprint#1092) - LSP proxy — spawns per-profile
dprint lspbackends, routes requests by file URI - LSP URI rewriting — format extensionless files (shell scripts, etc.) by appending the
correct extension based on editor's
languageId - Directory arguments — pass directories to
fmt/check, scoped via dprint's file discovery - Transparent drop-in — symlink as
dprint, all unknown commands passthrough to the real binary
Config file: ~/.config/dprint/dprintx.jsonc
Rules in match are evaluated top-to-bottom, first match wins. Files not matching any rule are skipped. Use
"**": "profile" as a catch-all. Profiles set to null cause the file to be skipped (passed through unchanged).
All paths in the config (dprint, profile paths) support ~ expansion and relative paths. Relative paths are resolved
against the directory containing dprintx.jsonc:
{
"dprint": "bin/dprint", // → ~/.config/dprint/bin/dprint
"profiles": {
"main": "profiles/main.jsonc", // → ~/.config/dprint/profiles/main.jsonc
},
}match_content lets you override the path-matched profile based on file content. This is useful for skipping generated
files that live alongside hand-written code.
{
"match_content": {
"^// Code generated .+ DO NOT EDIT": "ignore",
"^# Code generated .+ DO NOT EDIT": "ignore",
},
}How it works:
- File is first matched by path (
matchrules) — if no path match, the file is skipped entirely - If
match_contentis configured, the file is scanned in line-aligned blocks (~8KB each) - Each regex pattern is tested against each block, first match wins (stops scanning early)
- The matched profile overrides the path-based result
Patterns are regular expressions (Rust regex syntax, multi-line mode: ^ matches start of any line).
When diff_pager is set, dprint check produces unified diff output instead of dprint's default format:
- stdout is TTY → pipes through the pager (e.g.
delta -s) - stdout is pipe/redirect → raw unified diff
dprintx check # pretty diff via delta
dprintx check > fix.patch # unified diff to fileWithout diff_pager, dprint check behaves exactly like the original dprint.
Projects can define local formatting rules that override the matched profile.
How it works:
- For each file being formatted, dprintx walks up the directory tree looking for
dprint.jsonordprint.jsonc(stops at the first one found) - If found, it reads the local config and injects the matched profile path into
extends - A temporary merged config is written and passed to dprint instead of the profile config
- The temp file is auto-deleted when the command finishes (RAII guard)
Since dprint applies extends first and then overlays local settings on top, the local config takes precedence.
Example:
// ~/projects/my-app/dprint.json — only the overrides you care about
{
"yaml": {
"commentSpacing": "ignore",
},
}When formatting files under ~/projects/my-app/, dprintx generates a temporary config equivalent to:
{
"extends": "/home/user/.config/dprint/dprint-default.jsonc",
"yaml": {
"commentSpacing": "ignore",
},
}extends handling:
Local config extends |
Result |
|---|---|
| absent | set to profile path |
"https://example.com/base" |
["/profile/path", "https://example.com/base"] |
["a.json", "b.json"] |
["/profile/path", "a.json", "b.json"] |
The profile path is always prepended so that local settings win.
Temp file location: $XDG_RUNTIME_DIR/dprintx/ (per-user, mode 700). Falls back to $TMPDIR/dprintx/ if
XDG_RUNTIME_DIR is unavailable. Files are named merged-{pid}-{seq}.json and cleaned up automatically.
If no local config is found, the profile config is used directly — no temp file is created.
dprint doesn't support directories as arguments (dprint check src/ gives "Is a directory" error). dprintx handles
directory arguments by using dprint's own file discovery (output-file-paths) filtered to the specified directories:
dprintx fmt src/ # format all matched files under src/
dprintx check pkg/internal/drafts # check all matched files under the directory
dprintx fmt a.go src/ b.rs # mix of files and directories works tooFiles are passed through as-is. Directories use the same pipeline as dprintx fmt/dprintx check without arguments —
dprint discovers files via its own includes/excludes, then dprintx filters by profile match rules. This naturally skips
binary files, build artifacts, and anything dprint wouldn't process on its own.
Disabled by default for compatibility. Enable explicitly with
"lsp_rewrite_uris": true.
dprint matches files by extension, so extensionless files (e.g. shell scripts named myscript, Lua scripts without
.lua) are silently skipped during LSP formatting.
When lsp_rewrite_uris is enabled, the proxy tracks languageId from textDocument/didOpen and rewrites URIs
forwarded to the dprint backend by appending the correct extension (e.g. file:///path/myscript →
file:///path/myscript.sh for languageId=sh). If the file already has the correct extension, no rewrite happens.
{
"lsp_rewrite_uris": true,
}Default: false (transparent passthrough).
Supported languages:
| languageId | Extension |
|---|---|
| go | .go |
| lua | .lua |
| json | .json |
| jsonc | .jsonc |
| yaml | .yaml |
| markdown | .md |
| python | .py |
| rust | .rs |
| typescript | .ts |
| typescriptreact | .tsx |
| javascript | .js |
| javascriptreact | .jsx |
| sh / bash / zsh | .sh |
| toml | .toml |
| css | .css |
| html | .html |
| sql | .sql |
| dockerfile | .Dockerfile |
| graphql | .graphql |
# stdin — single file, filename is used for config matching (input is read from stdin)
dprintx fmt --stdin path/to/file.yaml < input.yaml
# fmt/check — groups files by profile, calls dprint per group
dprintx fmt
dprintx check
dprintx fmt file1.go file2.yaml # explicit file list
dprintx check src/ # directory → recursively expanded
# list all files that would be formatted (merged from all profiles)
dprintx output-file-paths
# show which config is used
dprintx config # all profiles and rules
dprintx config path/to/file # resolved config for a file
# LSP proxy — spawns dprint lsp per profile, routes by file URI
dprintx lspdprintx check exits with code 1 if any files need formatting.
Use --config <PATH> to override the config location (default: ~/.config/dprint/dprintx.jsonc):
dprintx --config /path/to/custom.jsonc fmtAll unknown commands and flags are passed through to the real dprint (--help, -V, license, completions, etc.).
cargo install --git https://github.com/mocksoul/dprintxSymlink dprintx as dprint earlier in your PATH — it becomes a fully transparent drop-in replacement. All unknown
commands and flags are forwarded to the real dprint binary (configured via "dprint" in dprintx.jsonc):
ln -sf ~/.cargo/bin/dprintx ~/.local/bin/dprintNow dprint fmt, dprint check, dprint lsp etc. all go through dprintx automatically. No changes needed in editor
configs, CI scripts, or muscle memory.
{ "dprint": "~/.cargo/bin/dprint", "diff_pager": "delta -s", "lsp_rewrite_uris": true, "profiles": { "maintainer": "~/.config/dprint/dprint-maintainer.jsonc", "default": "~/.config/dprint/dprint-default.jsonc", "ignore": null, // null = skip file entirely }, "match": { "**/noc/cmdb/**": "maintainer", "**/noc/invapi/**": "maintainer", "**": "default", }, "match_content": { "^// Code generated .+ DO NOT EDIT": "ignore", }, }