Formatting
Format JavaScript, TypeScript, HTML, Vue, Angular templates, CSS, JSON, YAML, Markdown, GraphQL, and Handlebars with bun format, a fast formatter that is compatible with Prettier
bun format formats JavaScript, TypeScript, HTML, Vue, Angular templates, CSS, JSON, YAML, Markdown, GraphQL, and Handlebars the way Prettier does. It reads your .prettierrc, and uses every CPU core.
bun formatsrc/index.ts
src/server.ts
Formatted 2 files, 118 unchanged [21.00ms]bun format writes the files it changes and prints their names. You don't need the prettier package, and you don't need a configuration file.
To find out whether files are formatted without changing them, use --check.
bun format --checkChecking formatting...
[warn] src/index.ts
[warn] Code style issues found in the above file. Run bun format to fix.Replace Prettier#
bun format reads the same configuration, formats the same files, and takes the same flags. The one difference: it writes unless you tell it not to.
Change the format scripts
{
"scripts": {
"format": "bun format",
"format:check": "bun format --check"
}
}prettier --write . becomes bun format, and prettier --check . becomes bun format --check. Keep the other flags and the arguments.
Change the format script before you try the command. While the script says prettier, bun format runs the script, so it runs Prettier.
Check that nothing changes
bun run format --checkChecking formatting...
All matched files use Prettier code style!A run that ends with exit code 2 names the files or the plugin that bun format left out.
Change CI, lint-staged, and Git hooks
Replace prettier, npx prettier, and bunx prettier with bun format. See Run in CI and Format before you commit.
Remove the packages
bun remove prettierRemove the plugins that are built in too: the three that sort imports. Leave their names in plugins.
Keep prettier in any of these cases:
- The Prettier extension of your editor uses it.
eslint-plugin-prettierruns it.- A plugin formats files in a language that
bun formatdoes not have, like Astro:bun formathands them to it.
| Instead of | Run |
|---|---|
prettier --write . | bun format |
prettier --write src test | bun format src test |
prettier --check . | bun format --check |
prettier -l . | bun format -l |
prettier --stdin-filepath a.ts | bun format --stdin-filepath a.ts |
bun format takes every flag of Prettier 3 except --file-info and --support-info.
bun format formats .js, .mjs, .cjs, .jsx, .js.flow, the other names that Prettier takes for JavaScript (.es6, .jsm, Jakefile, ..., and a file without an extension that starts with #!/usr/bin/env node), .ts, .mts, .cts, .tsx, .json, .jsonc, .json5, .css, .scss, .less, .yaml, .yml, .md, .mdx, .graphql, .gql, .hbs, .handlebars, .html, .htm, .xhtml, .vue, .component.html, and .mjml files. A .js file with a @flow comment at the top is read as Flow. Scripts, styles, and expressions in HTML, Vue, and Angular templates are formatted too. So is HTML in html`...` and /* HTML */ `...` templates, in the template of an Angular @Component, and in html, vue, and angular-html code blocks in Markdown; --embedded-language-formatting=off leaves those as they are.
Svelte, Astro, and the other languages of plugins go over a bridge. If the configuration of Prettier names a plugin that adds a language, like prettier-plugin-svelte, and prettier and the plugin are installed, bun format hands the files of that language to them, with the configuration file and the flags, and checks or writes what comes back. The output is Prettier's, and so is the speed. Prettier stays installed for these files, with every plugin that the configuration names, the built-in ones too. The run says how many went that way.
Without the packages, and with an .oxfmtrc.json, it leaves such files as they are. It formats the rest, and then ends with an error that says how many files of which kind it left, and exit code 2: a check that skips files in silence is not the check you replaced. --allow-unsupported makes that a warning.
Replace oxfmt#
bun format reads .oxfmtrc.json, .oxfmtrc.jsonc, and oxfmt.config.ts, including overrides, ignorePatterns, and insertFinalNewline, and takes the flags of oxfmt.
Change the format scripts
{
"scripts": {
"format": "bun format",
"format:check": "bun format --check"
}
}Keep the flags and the arguments. Change the format script before you try the command. While the script says oxfmt, bun format runs the script, so it runs oxfmt.
Check that nothing changes
bun run format --checkChecking formatting...
All matched files use the correct format.A project that pins a version of oxfmt before 0.72 has a few files reformatted once, as when it upgrades oxfmt.
Change CI, lint-staged, and Git hooks
Replace oxfmt, npx oxfmt, and bunx oxfmt with bun format.
Write a configuration file if there is none
bun run format --initWithout a configuration file, bun format knows from the oxfmt dependency that it stands in for oxfmt. --init writes an .oxfmtrc.json, so the choice stays when you remove the package.
Remove the package
bun remove oxfmtKeep oxfmt if the oxc extension of your editor uses it. bun format has no language server.
| Instead of | Run |
|---|---|
oxfmt | bun format |
oxfmt --check . | bun format --check |
bun format takes every flag of oxfmt 0.72 except --lsp and --migrate. See Differences from oxfmt.
Without any configuration file, bun format is like oxfmt if the nearest package.json has oxfmt or vite-plus among its dependencies and not prettier, or if you pass --flavor oxfmt. With vite-plus, the options are the fmt of the nearest vite.config.ts that has one, as for vp fmt.
If the configuration file nearest to the working directory is one of these, bun format finds files and configuration the way oxfmt does:
- Lines are 100 columns wide unless
printWidthsays otherwise. - Every
.gitignoreof the repository counts, not only the one in the current directory. - A
!argument andignorePatternsare in the format of.gitignore. - Only the
.editorconfignearest to the current directory counts. .prettierrcfiles are not read.-c <path>is--config <path>.- Which files are read goes by oxfmt's list of names, which tells upper case from lower case:
a.MDis not Markdown. - A configuration file that cannot be used ends the run with exit code
1.
sortImports, sortPackageJson, and jsdoc work as in oxfmt. sortTailwindcss sorts the classes in JavaScript, TypeScript, CSS, SCSS, Less, HTML, Vue, Angular, Handlebars, and Markdown files, in the order of the tailwindcss that the project has installed, version 3 or 4.
TOML files (*.toml, *.toml.example, Pipfile, Cargo.toml.orig) are formatted as oxfmt formats them: keys and values stay as they are written, white space, empty lines and the layout of arrays change. A file that is not valid TOML is left as it is, with a warning that says what is wrong with it, where oxfmt says nothing and rewrites the rest of it.
Where oxfmt prints something other than Prettier, bun format prints what oxfmt 0.72 prints, for every file whose configuration file is one of oxfmt's, wherever the run starts. A project that pins an older version has a few files reformatted once, as when it upgrades oxfmt.
Configuration#
For each file, bun format uses the nearest configuration file: it looks in the directory of the file, then in each parent directory. In a directory with several, the first of this list counts:
.oxfmtrc.json,.oxfmtrc.jsonc,oxfmt.config.ts"prettier"inpackage.jsonorpackage.yaml.prettierrc, in JSON or YAML.prettierrc.json,.yml,.yaml,.json5.prettierrc.js,prettier.config.js, and the same with.ts,.mjs,.mts,.cjs,.cts.prettierrc.toml
{
"semi": false,
"singleQuote": true,
"overrides": [{ "files": "*.test.ts", "options": { "printWidth": 120 } }]
}A configuration file can be a program. So where the working directory has none, in it or above it, a configuration file counts only for what is in or below the working directory, and for a file or a directory that you name. What a pattern such as "../**/*.js" finds elsewhere is formatted with the defaults. Prettier runs every configuration file that a pattern leads to.
See Prettier's options for what each option does. Plugins do not load. The three that sort imports are built in: see Sort imports. For any other, see Differences from Prettier.
A flag overrides the configuration file:
bun format --no-semi --print-width 100.editorconfig#
bun format reads indent_style, indent_size, tab_width, max_line_length, and end_of_line from .editorconfig, like Prettier. A configuration file overrides them. Use --no-editorconfig to ignore .editorconfig.
bunfig.toml#
The [format] section of bunfig.toml sets the defaults of the flags. A key is the name of the flag in camelCase, which for a formatting option is its name in a .prettierrc.
[format]
printWidth = 100 # --print-width 100
semi = false # --no-semi
ignorePatterns = ["vendor/", "*.gen.ts"] # --ignore-pattern vendor/ --ignore-pattern '*.gen.ts'The command line overrides bunfig.toml. bunfig.toml overrides the configuration file and .editorconfig, as a flag does. With configPrecedence = "prefer-file", the formatting options of [format] count only for a file without a configuration file.
Bun reads the bunfig.toml of the working directory. Write bun --config=other.toml format for another file.
See bunfig.toml for every key.
Sort imports#
bun format sorts imports itself. It does so if the configuration asks for it. A plugin that is named does not have to be installed:
| In the configuration file | Sorts like |
|---|---|
"plugins": ["@trivago/prettier-plugin-sort-imports"] | @trivago/prettier-plugin-sort-imports |
"plugins": ["@ianvs/prettier-plugin-sort-imports"] | @ianvs/prettier-plugin-sort-imports |
"plugins": ["prettier-plugin-organize-imports"] | prettier-plugin-organize-imports: TypeScript's "Organize Imports", which also removes unused imports |
sortImports in .oxfmtrc.json | oxfmt |
{
"plugins": ["@trivago/prettier-plugin-sort-imports"],
"importOrder": ["<BUILTIN_MODULES>", "<THIRD_PARTY_MODULES>", "^@/(.*)$", "^[./]"],
"importOrderSeparation": true,
"importOrderSortSpecifiers": true
}import { readFile } from "node:fs/promises";
import { z } from "zod";
import { db } from "@/db";
import { a, b } from "./util";Every option of the four works as it does there. Comments move with their imports the same way, and the comments that turn sorting off work too: // sort-imports-ignore, // prettier-ignore before an import, // organize-imports-ignore.
- Without its plugin in
pluginsor--plugin, an option likeimportOrderis ignored with a warning, as in Prettier. prettier-plugin-organize-importsleaves code blocks in Markdown and the scripts of Vue and HTML files alone. The other three sort code blocks and the scripts of Vue files. In HTML, the twosort-importsplugins sort scripts and oxfmt does not.importOrderParserPluginsis accepted and not needed:bun formatreads TypeScript, JSX, and decorators in every file.<BUILTIN_MODULES>are the modules of Node.js 26, whatever runsbun format.
Sort Tailwind CSS classes#
bun format sorts the classes of Tailwind CSS if sortTailwindcss in .oxfmtrc.json asks for it.
The order is that of the project's own tailwindcss, version 3 or 4, which has to be installed. bun format asks it once in a run.
Choose what to format#
Pass files, directories, or glob patterns. Without arguments, bun format formats the current directory.
bun format src/index.ts
bun format src test
bun format "src/**/*.ts" "!src/generated/**"bun format skips node_modules, .git, and what .gitignore and .prettierignore in the current directory ignore. Use --ignore-path to name other files.
dist
src/generatedTo keep a piece of code as it is, put a // prettier-ignore comment before it. In a project of oxfmt, // oxfmt-ignore does the same.
Run in CI#
bun format --check exits with code 1 if a file is not formatted, and 2 if something went wrong: a syntax error, an option with a value that it cannot have, files in a language that is not supported. Prettier exits with 1 for some of these.
name: Format
on: [push, pull_request]
jobs:
format:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: oven-sh/setup-bun@v2
- run: bun format --checkIf your configuration file imports a package, run bun install first.
Format before you commit#
With lint-staged:
{
"lint-staged": {
"*.{js,jsx,ts,tsx}": "bun format"
}
}Format in your editor#
An editor that can run a command on save can pipe the file through bun format. It prints the formatted code on stdout.
bun format --stdin-filepath src/index.ts < src/index.tsThe path decides the language, the configuration, and whether the file is ignored. The file does not have to exist.
With --check or --list-different, (stdin) is printed and the exit code is 1 if the code is not formatted. As in Prettier, code that cannot be parsed is reported with exit code 0. With an .oxfmtrc.json, as in oxfmt, neither flag goes with --stdin-filepath.
Code that bun format would leave as it is in a file, because of its language or of a plugin, is an error here too: nothing is printed, and the exit code is 2. With --allow-unsupported it is a warning.
If package.json has a format script#
Your script wins. bun format runs it, like bun run format. Inside that script, and in any script it starts, bun format is the formatter, so "format": "bun format" works.
The first line of the output tells you that the script ran: $ bun format.
In another script, bun format runs the format script too, with the flags added. So "format:check": "bun format --check" runs the format script with --check.
A file such as format.ts, a directory format/ with an index.js, or node_modules/.bin/format keeps the name too: bun format runs that, as it did before. A package called format somewhere in node_modules, or a directory called format without an index file, does not.
Differences from Prettier#
bun format aims to print exactly what Prettier 3 prints. If it prints something else, that is a bug in Bun. Open an issue on GitHub with code that shows the difference.
These differences are intentional:
- It writes by default.
prettierwithout--writeprints the formatted code.--writeis accepted and changes nothing. - No arguments means the current directory.
- Only changed files are printed, without the time each took, and a summary line goes to stderr.
- No Svelte or Astro of its own. Their files go to the project's own Prettier and its plugin, if they are installed. If not, they are left as they are, and the run ends with an error that counts them. With
--allow-unsupportedit is a warning. Asveltecode block in Markdown stays as it is. - Plugins do not load. The three that sort imports are built in. A plugin that only adds a language, like
prettier-plugin-astro, changes nothing for the rest: the files of that language are handed to the project's own Prettier. Those whoseparserin the configuration is that of a plugin thatbun formatdoes not know count as files in a language that is not supported. Any other plugin, likeprettier-plugin-brace-style, may print the files of the project in another way thanbun formatwould, so the files that its configuration file is for are left as they are, and the run ends with an error that names the plugin. With--allow-unsupportedthey are formatted without it. - An unknown flag is an error. Prettier warns and goes on.
- A file is checked before it is written.
bun formatparses what it is about to write and compares it with what it read. If the two are not the same program, it leaves the file as it is and reports a bug. - HTML is checked too. If what is about to be written does not have the same elements, attributes, and text as the file,
bun formatleaves the file as it is, reports it, and exits with 2. - Templates keep their expressions. Prettier closes
html`<${Tag}>`with a second${Tag}. If a template would end up with more or fewer${}than it has,bun formatleaves the file as it is, reports it, and exits with 2. - A Handlebars template that Prettier would damage is left as it is. Prettier deletes
<!DOCTYPE html>, joins the lines of an inline<script>, and turns{{this/name}}into{{name}}.bun formatreports such a file and exits with 2. - What follows a NUL in HTML is not deleted. For Prettier, a NUL between the tags of an HTML or Vue file is the end of the file: it deletes the rest.
bun formatleaves such a file as it is, reports it, and exits with 2. - Bytes that are not UTF-8 stay as they are. Prettier reads each of them as U+FFFD. When it writes a Latin-1 file, those characters are lost.
- Nothing is written through a link that leads out of the repository, or out of the working directory if that is in no repository. Prettier writes there. The run fails and names the file.
- A configuration file that leaves a timer or a server running does not keep the run alive. Prettier does its work and never exits.
- What a configuration file prints is shown only if it fails.
--cachehas no effect. Every run formats every file.--file-infoand--support-infoare errors.
Differences from oxfmt#
With an .oxfmtrc.json, bun format aims to print exactly what oxfmt 0.72 prints. If it prints something else, that is a bug in Bun. Open an issue on GitHub with code that shows the difference.
These differences are intentional:
- The output is that of oxfmt 0.72, whichever version you had installed.
- No language server, no migration.
--lspand--migrateare errors:bun format does not support --lsp. - No Svelte. With
sveltein the configuration,.sveltefiles are left as they are, and the run ends with an error that counts them. With--allow-unsupportedit is a warning. Asveltecode block in Markdown stays as it is. - Errors and warnings are in the words of Bun, with
[error]or[warn]before each line. What else is printed about a run is what oxfmt prints, on the same stream. - A TOML file that is not valid is left as it is, with a warning. oxfmt rewrites the rest of the file.
- A file that is not UTF-8 is formatted, and the bytes that are not UTF-8 stay as they are. oxfmt does not read such a file, and exits with 2.
- What follows a NUL in HTML is not deleted, as in Differences from Prettier.
- A file is checked before it is written, as in Differences from Prettier.
CLI usage#
bun format [flags] [...files, directories or patterns]| Flag | Description |
|---|---|
-c, --check | Do not write. Exit with 1 if a file is not formatted |
-l, --list-different | Do not write. Print the files that are not formatted |
--stdin-filepath <path> | Format standard input as that file, and print the result |
--config <path> | Use this configuration file instead of looking for one |
--no-config | Do not look for a configuration file |
--no-editorconfig | Do not read .editorconfig |
--config-precedence <which> | cli-override (default), file-override, or prefer-file |
--ignore-path <path> | Files with patterns to ignore. Default: .gitignore, .prettierignore |
--ignore-pattern <pattern> | Ignore the files that match, as a line of .prettierignore does |
--with-node-modules | Format files in node_modules too |
--no-error-on-unmatched-pattern | Do not fail if an argument matches no file |
-u, --ignore-unknown | Say nothing about a file that there is no parser for |
--allow-unsupported | Only warn about files in a language that is not supported |
--flavor <which> | Without a configuration file: prettier or oxfmt |
--init | Write an .oxfmtrc.json |
--find-config-path <path> | Print the configuration file of a file, and format nothing |
--log-level <level> | silent, error, warn, log (default), or debug |
--print-width, --no-semi… | Any formatting option, as for prettier. Overrides the configuration |
--require-pragma | Only format files that start with a @format or @prettier comment |
--check-ignore-pragma | Skip files that start with a @noformat or @noprettier comment |
--insert-pragma | Put a @format comment at the top of the files that are formatted |
--range-start, --range-end | Only format the statements between these offsets |
--cursor-offset <offset> | With --stdin-filepath: print on stderr where that offset ends up |
--parser <name> | Read every file with this parser of Prettier, whatever its name |
--disable-nested-config | Use the configuration file of the current directory for every file |
--threads <n> | Number of threads. Default: one per CPU core |
--timing | Print how long each phase took |
--cwd <path> | Set the working directory |
-h, --help | Print the help menu |