# bibtex-tidy: a formatter and cleaner for BibTeX files

> bibtex-tidy normalises BibTeX files, aligning field values, sorting entries, detecting duplicates and merging them, with a browser version, a CLI, a pre-commit hook, a JavaScript API and a Docker image. It suits anyone with a shared .bib file that has grown organically, and its behaviour is driven by a long list of flags rather than by a config file.

**FlamingTempura/bibtex-tidy** — Cleaner and Formatter for BibTeX files

- Repository: https://github.com/FlamingTempura/bibtex-tidy
- Website: https://flamingtempura.github.io/bibtex-tidy/
- Stars: 1,129 · Forks: 92
- Language: TeX
- License: MIT
- Published: 2026-10-19 · Updated: 2026-10-19 · Language: en
- Canonical page: https://hysenlabs.com/projects/flamingtempura-bibtex-tidy

## Five ways to run the same formatter

bibtex-tidy is a cleaner and formatter for BibTeX files, and the README lists five ways to use it. You can run it in the browser at the project site. You can use the CLI, which requires node v12 or later, and it installs as a global command:

```sh
npm install -g bibtex-tidy
bibtex-tidy references.bib
``` You can install it as a pre-commit hook, and the repository root carries a `.pre-commit-hooks.yaml` for exactly that. You can call it from JavaScript or TypeScript through its API. Or you can run it in Docker. That range is the interesting part of the design, because a formatting tool is only useful if it sits where the file already lives, and the same person might edit a.bib in a browser on a conference website, in a pull request on GitHub, and on a shared server. The primary language is listed as TeX, the licence is MIT, and the project is not archived with the last push on 2026-10-18 and no GitHub releases published, the npm version being 1.15.1.

## What tidying actually changes

The before and after in the README makes the transformation concrete. The input has an entry type in capitals with a space before the brace, inconsistent spacing around the equals signs, fields in arbitrary order, a trailing comma after the last field and a closing brace on the same line. The output is a lowercase entry type with no space, one space around the equals sign with the values aligned in a column, the fields reordered with year before journal and publisher, no trailing comma, and the closing brace on its own line. The second entry, which is indented with four spaces and has no trailing comma, comes out with the same two-space indent and the same alignment. The second entry keeps its `journal` field with the value `Beverly Hills`, which is really the publisher, not a journal. That is a good reminder that the tool reformats and does not understand semantics, and a formatter that also fixed the misfiled field would be doing more than formatting.

## The v1 default of overwriting your file

The single most important thing to know before running the CLI is what it does to your input. The `--v2` option documents the change: input files will no longer be modified by default, and you will have to specify `--modify` or `-m` to overwrite the file, or `--output` or `-o` to output to a different file. The `--modify`, `-m` and `--no-modify` flags describe the current v1 behaviour, where overwriting is enabled by default and you use `--no-modify` to output to stdout instead. The `--output` flag likewise says that when it is omitted and modify is not used, the result is printed to stdout. So in v1, running `bibtex-tidy references.bib` writes back to the file, and a version 2 release is planned that changes that default. The safe habit in v1 is to pass `--no-modify` and redirect, or to work on a copy.

## Duplicate detection and the four merge strategies

The `--duplicates` option warns if duplicates are found, and the definition of a duplicate is spelled out. Entries count as duplicates where the DOI, the abstract, or the author and title are the same. The option takes a comma separated list, so `--duplicates=doi` for same DOIs, `--duplicates=key` for same IDs, `--duplicates=abstract` for similar abstracts, `--duplicates=citation` for similar author and titles, and a bare `--duplicates` means same DOI, key, abstract or citation. The `--merge` option goes further and merges the duplicates, using the duplicates option to decide how they are identified. There are four strategies. `first` keeps only the original entry. `last` keeps only the last found duplicate. `combine` keeps the original entry and merges in fields of the duplicates if they do not already exist. `overwrite` keeps the original entry and merges in fields of the duplicates, overwriting existing fields if they exist. The difference between `combine` and `overwrite` is the whole decision, since one fills gaps and the other replaces what you already had.

## Sorting entries and sorting fields

There are two separate sorting mechanisms, and they are easy to confuse. `--sort` sorts whole entries by the specified field names, using the citation key if no fields are specified, and a descending order is requested by prefixing the field with a dash. Multiple fields sort by the first field, then by the second whenever the first field is equal for entries. Three extra field names are permitted: `key` for the entry citation key, `type` which sorts by the type of entry such as article, and `special` which ensures that `@string`, `@preamble`, `@set` and `@xdata` entries come first. The examples show `--sort` for the citation key, `--sort=-year,name` for year descending then name ascending, and `--sort=name,year`. Separately, `--sort-fields` sorts the fields within an entry, and with no fields specified the order is title, shorttitle, author, year, month, day, journal, booktitle, location, on, publisher, address, series, volume, number, pages, doi, isbn, issn, url, urldate, copyright, category, note, metadata. That default list is a citation-style ordering, and you can override it with something like `--sort-fields=name,author`.

## Formatting flags, and the ones that change your data

Most flags are cosmetic, and a few are not. `--curly` encloses all property values in braces and converts quoted values, so "Journal of Tea" becomes {Journal of Tea}, while `--numeric` strips quotes and braces from numeric and month values, so {1998} becomes 1998. `--months` converts all months to three letter abbreviations. `--strip-enclosing-braces` removes the extra braces where an entire value is enclosed in double braces, so {{Journal of Tea}} becomes {Journal of Tea}. `--drop-all-caps` title-cases values that are all caps, so {JOURNAL OF TEA} becomes {Journal of Tea}, leaving Roman numerals unchanged. `--escape` and `--no-escape` control escaping of special characters such as umlauts, enabled by default, with the note that this is usually unnecessary for modern toolchains like biber and biblatex. There is a legacy escape list used for v1 compatibility that may emit macros requiring external packages, and `--escape=new` switches to package-independent LaTeX escapes while warning on characters that cannot be escaped. New mode will become the default in v2, and the documentation asks you to raise a GitHub issue if you still need the legacy behaviour. `--unescape` goes the other way, converting LaTeX escapes to Unicode characters.

## Layout, whitespace and URL handling

The layout flags control the shape of the file. `--space` indents all fields with the specified number of spaces, with 2 as the default and 4 as the example, and it is ignored if tab is set. `--tab` indents with a tab. `--align` inserts whitespace between fields and values so the values are visually aligned, with 14 as the default column. `--blank-lines` inserts an empty line between each entry, and `--trailing-commas` ends the last key value pair in each entry with a comma. On comments, `--strip-comments` removes all comments from the BibTeX source, and `--tidy-comments` removes whitespace surrounding comments, which is the gentler of the two and the one you probably want in a repository where the comments carry information. `--encode-urls` replaces invalid URL characters with percent encoded values, and `--omit` removes specified fields from bibliography entries, for example `--omit=id,name`.

## The package layout, and the browser build

The npm package ships four files, and the manifest is unusually explicit about them. The `files` array lists `bin/bibtex-tidy`, `bibtex-tidy.js`, `bibtex-tidy.d.ts` and `bibtex-tidy.0`, with `main`, `module` and `types` pointing at the first three and the man page as `man`. The `bin` entry maps the `bibtex-tidy` command to `./bin/bibtex-tidy`. The package is an ES module with Node `>12` as the engine requirement. The build is a single command, `node build.ts`, with a `serve` variant that starts a development server, and the repository has a `docker/` directory. The browser version is not a separate codebase, it is the same code bundled by esbuild and Svelte, since the dev dependencies include `@sveltejs/vite-plugin-svelte`, CodeMirror with a Lezer grammar, and `@testing-library/svelte`. The grammar is generated from `src/ui/codemirrorExtensions/bibtex.grammar` through a `codemirror-grammar` script, which is how the online editor gets syntax highlighting.

## Conclusion

Adopt bibtex-tidy if your .bib files are shared and drifting, since the alignment, sorting, duplicate detection and merge options turn an unreadable file into a reviewable one, and the pre-commit hook keeps it that way. Skip it if you want a BibTeX parser with a programmatic API for reading entries, since this formats rather than exposes a data model. Before you adopt the CLI, note that in v1 it overwrites the input files by default and you need `--no-modify` to write to stdout, that `--v2` flips that default, and that the escaping default uses the legacy list for v1 compatibility, so pass `--escape=new` if you are on biber or biblatex.

## FAQ

### Does bibtex-tidy modify my file in place?

In v1 it does, and overwriting the input files is enabled by default. Use --no-modify to print to stdout instead, or --output to write elsewhere. The --v2 flag shows the planned change, where input files will no longer be modified by default and you need --modify or --output.

### How does bibtex-tidy detect duplicate entries?

Entries count as duplicates where the DOI, the abstract, or the author and title are the same. The --duplicates option takes a comma separated list such as doi, key, abstract or citation, and a bare --duplicates checks all of them.

### What is the difference between the combine and overwrite merge strategies?

Both keep the original entry and merge in fields of the duplicates, but combine only fills in fields that do not already exist, while overwrite replaces existing fields with the values from the duplicates. The other strategies are first, which keeps only the original, and last, which keeps only the last duplicate found.

### How do I install bibtex-tidy as a command line tool?

Run `npm install -g bibtex-tidy` and then `bibtex-tidy references.bib`. The CLI requires node v12 or later, and the package also ships a pre-commit hook definition, a browser version, a JavaScript API and a Docker image.

### When should I use --escape=new in bibtex-tidy?

The default legacy escape list is used for v1 compatibility and may emit macros requiring external packages, which is usually unnecessary for modern toolchains like biber and biblatex. --escape=new converts only characters with package-independent LaTeX escapes and warns on the ones it cannot escape, and it becomes the default in v2.

## Sources

- [FlamingTempura/bibtex-tidy on GitHub](https://github.com/FlamingTempura/bibtex-tidy)
- [Issues](https://github.com/FlamingTempura/bibtex-tidy/issues)
- [License: MIT](https://github.com/FlamingTempura/bibtex-tidy/blob/master/LICENSE)
- [Project website](https://flamingtempura.github.io/bibtex-tidy/)
- [README](https://github.com/FlamingTempura/bibtex-tidy/blob/master/README.md)

---

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