Migrate from Prettier to bun fmt

bun fmt reads your .prettierrc and prints what Prettier 3 prints. In most projects you only change the command.

Start with --doctor. It shows what Bun will do with your config, and it doesn't format or write anything.

terminal
bun fmt --doctor
Using .prettierrc (Prettier config)

    4 files are formatted natively

Built-in plugins: @ianvs/prettier-plugin-sort-imports

You can remove: @ianvs/prettier-plugin-sort-imports
Still needed:   prettier (the "format" script runs it)
Not checked:    other tools' configs, CI files, Git hooks

Change the scripts.

package.json
{
  "scripts": {
    "format": "prettier --write .", 
    "format:check": "prettier --check .", 
    "format": "bun fmt", 
    "format:check": "bun fmt --check" 
  }
}

bun fmt writes by default and formats the current directory, so --write . goes away. Other flags and arguments stay the same. Change CI and Git hooks too.

If your script is called fmt, don't skip this: bun fmt runs a fmt script when package.json has one, so you'd still get Prettier.

Watch out for two differences in hooks and shell scripts:

  • No arguments means the current directory. Prettier prints an error there. So if the file list can be empty, as in git diff --cached --name-only | xargs bun fmt or bun fmt $(git ls-files '*.foo'), Bun formats everything. Use bun fmt --staged in a pre-commit hook, or xargs -r.
  • bun fmt file.ts writes the file and prints its name. prettier file.ts prints the formatted code. To print it, pipe the code in: bun fmt --stdin-filepath file.ts < file.ts.

Piping into --parser works like in Prettier: generate | bun fmt --parser typescript > out.ts.


Check that nothing changes. With a Prettier config, --check prints what Prettier prints.

terminal
bun fmt --check
$ bun fmt --check
Checking formatting...
All matched files use Prettier code style!

Run --doctor again. No script runs Prettier anymore, so it can go too.

You can remove: @ianvs/prettier-plugin-sort-imports, prettier
Not checked:    other tools' configs, CI files, Git hooks
terminal
bun remove prettier @ianvs/prettier-plugin-sort-imports

Leave .prettierrc as it is, including plugins.

Keep prettier if your editor's Prettier extension uses it.


Bun reads everything Prettier reads:

  • .prettierrc in JSON, YAML, TOML, JavaScript, or TypeScript, prettier.config.*, and "prettier" in package.json, with overrides
  • .editorconfig, and the .prettierignore and .gitignore in the current directory
  • // prettier-ignore comments and the @format and @noformat pragmas
  • Every flag of Prettier 3 except --file-info and --support-info

Bun doesn't load Prettier plugins. These are built in, with all their options, so leave them in plugins:

  • prettier-plugin-tailwindcss
  • @trivago/prettier-plugin-sort-imports
  • @ianvs/prettier-plugin-sort-imports
  • prettier-plugin-organize-imports
  • @prettier/plugin-oxc
  • prettier-plugin-svelte
  • prettier-plugin-packagejson

Svelte files are formatted the way prettier-plugin-svelte 4.1.1 formats them, whichever version you have installed. Other versions print a few components differently, so expect a small diff the first time if you were on one.

The plugin's options are those of 4.1.1: svelteSortOrder, svelteAllowShorthand, and svelteIndentScriptAndStyle. 4.1.1 has no svelteStrictMode, so Bun ignores it with a warning, like any unknown option.

With rangeStart or rangeEnd, a Svelte file is left unchanged, like an HTML or CSS file, because Prettier can't format part of one.

To leave Svelte files alone, set svelte = false in bunfig.toml.

package.json keys are sorted the way prettier-plugin-packagejson 3.0.2 sorts them with sort-package-json 3.7.1, and packageSortOrder is supported.

Bun puts the keys where the plugin puts them and leaves every value as you wrote it. The plugin parses the file and writes it back, which turns 1.0 into 1, 12345678901234567890 into 12345678901234567000, and "\u00e9" into "é". It also drops all but the last of two members with the same key, and moves keys like "10" to the front of their object. Bun does none of that. It also sorts a file with a trailing comma, where the plugin throws.


Astro and other languages that come from a plugin go to your installed Prettier. Keep prettier and every plugin in plugins installed. --doctor shows which files those are.

Using .prettierrc (Prettier config)

    3 files are formatted natively
    1 file goes to your installed Prettier: 1 *.astro

Built-in plugins: @ianvs/prettier-plugin-sort-imports
Plugins that use your installed Prettier: prettier-plugin-astro

Still needed:   @ianvs/prettier-plugin-sort-imports, prettier, prettier-plugin-astro (some files go to them)

With any other plugin, Bun skips the files that config covers and exits with code 2. A plugin could print them differently, and Bun won't guess. Pass --allow-unsupported to format them without the plugin.

error: 3 files were not formatted because the Prettier config uses plugins that bun fmt does not include: prettier-plugin-brace-style
  Use --allow-unsupported to format them without the plugins.

You don't have to keep .prettierrc. Move the options to bunfig.toml, under the same names, and delete the file. overrides become [[fmt.overrides]].

bunfig.toml
[fmt]
semi = false
singleQuote = true

Without a Prettier config, bun fmt also formats TOML files, formats Svelte files without the plugin in a config, and --check prints a shorter report.


Differences from Prettier#

If bun fmt and Prettier 3 print different code, that's a bug. Open an issue. These differences are intentional:

  • It writes by default. --write is accepted and does nothing.
  • No arguments means the current directory.
  • Only changed files are listed, followed by a summary on stderr.
  • An unknown flag is an error. Prettier warns and continues.
  • --cache does nothing. Every run formats every file.
  • Exit code 2 means something went wrong, like a syntax error or a skipped file. Prettier uses 1 for some of these.
  • Bytes that aren't valid UTF-8 are kept. Prettier replaces them.
  • Where Prettier's output changes what a file means, Bun's differs. That's rare. In a Handlebars template Prettier drops <!DOCTYPE html>, and it joins the lines of a <script>, so a // comment swallows the code after it. In CSS it turns Font\ Awesome into Font\Awesome. In Svelte the plugin drops the comment in {#if a /* b */}, the space in <hr />some <b>words</b>, and an empty {:catch}, and it empties the <script> after a comment that mentions <style>. Bun keeps all of them.
  • A function's only parameter doesn't move on the second run. When an object type in the return type doesn't fit on the line, Prettier gives the parameter its own line, then joins m(a: string): { again the next time it runs. Bun prints the joined form the first time.
  • Symlinks that lead out of the repository aren't written through.
  • console.log in your config file is hidden unless the config fails to load.

See also: