# tex-fmt: a LaTeX formatter in Rust that also runs in the browser

> tex-fmt formats LaTeX source, handling indentation, line wrapping and table alignment, and it is distributed as a Rust binary, a WebAssembly build you can try in a browser, a CTAN package and a pre-commit hook. What makes it more than a style preference is the escape hatches: per-line and per-block skips, and verbatim environments left alone automatically.

**WGUNDERWOOD/tex-fmt** — An extremely fast LaTeX formatter written in Rust

- Repository: https://github.com/WGUNDERWOOD/tex-fmt
- Website: https://wgunderwood.github.io/tex-fmt/
- Stars: 881 · Forks: 58
- Language: Rust
- License: MIT
- Published: 2026-10-20 · Updated: 2026-10-20 · Language: en
- Canonical page: https://hysenlabs.com/projects/wgunderwood-tex-fmt

## What it formats, and what it leaves alone

tex-fmt is an extremely fast LaTeX formatter written in Rust, handling indentation, line wrapping and other formatting for LaTeX source code. The before and after in the readme is the clearest statement of the style it applies, and it is a modest one: the itemize environment is indented by two spaces per level, the equation body is indented by two spaces, and the blank lines between sections are preserved. What is more interesting is what it refuses to touch. Verbatim environments including verbatim, Verbatim, lstlisting and minted are automatically skipped, so code blocks and shell sessions in a paper come out unchanged without you saying anything. The rest of the feature list is short: extremely fast run-time performance, minimal configuration required, a command line interface, handling of the LaTeX file types .tex, .bib, .cls and .sty, and being written entirely in safe Rust. The MIT licence applies and the last push was on 2026-08-22.

## Four ways to install it, depending on your system

The installation section is thorough because the project is used on every kind of machine, and there are five distinct routes. Cargo is the first, installing the stable release from crates.io, or the GitHub source with the git flag. Nix users have two: a temporary shell with nix-shell, or a flake build with nix build. Arch Linux is covered by the AUR, with the yay helper given as the example. Debian has the package in the archive for trixie and later. Homebrew has a formula. And binaries for various platforms are downloadable from the GitHub releases page. The readme also links the packaging status through repology, which is a small sign that the project takes distribution seriously. The one that is not a system package is the browser, because the crate is built as a WebAssembly library as well as a binary.

## The browser build and the WebAssembly feature flag

The manifest explains the browser build. The library is declared with crate-type of cdylib and rlib, and the wasm feature pulls in js-sys and wasm-bindgen, so the same code that formats files can be compiled to WebAssembly and run in a page. The justfile shows the full pipeline, which is worth reading because it is the only place the build for the web is described. It creates the web package directory, builds the library in release mode with the wasm feature for the wasm32-unknown-unknown target, runs wasm-bindgen with the web target into the package directory, and then runs wasm-opt with the -Oz flag to shrink the generated module. The web directory in the repository is what the browser demo is served from, which is why the readme can tell you to try it in your browser without an install step. The feature list in the manifest is only two entries, shellinstall and wasm, which is a sign that the dependency list is kept small on purpose.

## The command line modes that matter

The usage section lists the common options and each one has a distinct purpose. Formatting a file overwrites it, and --check reports whether a file is already correctly formatted without changing it, which is the flag you want in continuous integration. --print formats to standard output instead of writing, --recursive formats files in the current directory or in a named directory, --fail-on-change formats the file but returns exit code 1 if it was overwritten, so a commit hook can refuse the change. --nowrap disables line wrapping, --format-tables formats tables by aligning ampersands, and --stdin reads from standard input and prints to standard output, which is what makes the tool usable in a pipeline. The set is short enough to hold in your head, and the split between a mutating mode and a checking mode is the important structural decision.

## Configuration files, and the priority order

Options can also be set in a configuration file, and the readme lists the locations in order of decreasing priority, which is the detail to get right when a setting does not take effect. A named config file passed with --config has the highest priority, then a tex-fmt.toml in the current working directory, then one in the root directory of the current git repository, then one in a tex-fmt subdirectory of the user's configuration directory, which on Linux is under .config, on macOS under Application Support and on Windows under AppData/Roaming. Arguments passed on the command line always override the configuration files. To ignore all config files there is a --noconfig flag, and there is a warning aimed at contributors: this repository's own configuration file is applied automatically if tex-fmt is run from inside the repository, so contributors should use --noconfig or an explicit --config to avoid that.

## Skipping parts of a document, line by line or block by block

The escape hatches are what make a formatter usable on an existing document, and there are two levels. Ending a source line with a comment marker disables formatting for that line. To disable formatting for a block, you use an off marker and an on marker around it:

``` tex
\documentclass{article}

\begin{document}

    This line is skipped % tex-fmt: skip

% tex-fmt: off
  These lines are also
    not formatted or wrapped
% tex-fmt: on

\end{document}
```

Note that the block markers are comments too, so they survive in the output and the region between them keeps whatever indentation it had. The other half of the behaviour is automatic: the verbatim environments are skipped without any marker at all, which covers the common case of code listings in a paper. The recursive mode also respects ignore files, following git conventions, so files matched by .gitignore or .ignore are not formatted when you use --recursive or -r.

## Editors, hooks and a package on CTAN

The integration story is what a formatter project lives or dies by, and tex-fmt has the usual set. For Visual Studio Code the integration is provided by the LaTeX Workshop extension, and you have to install tex-fmt yourself through one of the package routes first. For Neovim there is a package provided through mason.nvim. For GitHub Actions there is a separate tex-fmt-action that can install and run the tool. And there is a CTAN package named latex-formatter, which means the tool is installable by people who work in LaTeX distributions rather than by package managers. The pre-commit hook is a pre-commit configuration in your repository root, and the hook is pinned to a revision, with the id tex-fmt. If you want the hook to check rather than modify, you add the check argument to it. The readme also notes that shell completion scripts and a man page can be generated at run time with the completion and man flags, which is why the repository has completion and man directories.

## Conclusion

tex-fmt suits a LaTeX user who wants a formatter that is fast enough to run on every commit and that does not reformat the parts of a document which are supposed to stay as written, which is the usual reason people give up on LaTeX formatters. It does not suit a project that wants a specific house style it can configure beyond the documented options, and the wrapping behaviour is a choice rather than a setting. Before you adopt it, try it with the check flag first, since the tool rewrites files in place, and add the skip and off directives around any verbatim-like block it does not already recognise.

## FAQ

### How do I install tex-fmt?

With cargo install tex-fmt for the crates.io release or cargo install --git for the GitHub source, with nix-shell or a flake build on Nix, with yay on Arch Linux, with apt on Debian trixie and later, with brew on macOS, or by downloading a binary from the GitHub releases page.

### Does tex-fmt reformat code listings and verbatim blocks?

No. Verbatim environments including verbatim, Verbatim, lstlisting and minted are skipped automatically. For anything else you can disable formatting for a single line with a skip comment at the end of it, or for a block with an off marker and an on marker around the region.

### How do I use tex-fmt in a pre-commit hook without changing my files?

Add the tex-fmt hook from the project's own repository to your .pre-commit-config.yaml, pinned to a revision, and give it the check argument so it reports rather than rewrites. The check flag is also the one to use in continuous integration for the same reason.

### Where does tex-fmt look for its configuration file?

In order of decreasing priority, a named file passed with --config, a tex-fmt.toml in the current working directory, one in the root of the current git repository, and one in a tex-fmt subdirectory of the user configuration directory, which is .config on Linux, Application Support on macOS and AppData/Roaming on Windows. Command line arguments always win, and --noconfig ignores all of them.

## Sources

- [License: MIT](https://github.com/WGUNDERWOOD/tex-fmt/blob/main/LICENSE)
- [Project website](https://wgunderwood.github.io/tex-fmt/)
- [README](https://github.com/WGUNDERWOOD/tex-fmt/blob/main/README.md)
- [Releases](https://github.com/WGUNDERWOOD/tex-fmt/releases)
- [WGUNDERWOOD/tex-fmt on GitHub](https://github.com/WGUNDERWOOD/tex-fmt)

---

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