Guide
Prettier fundamentals explained
Prettier is an opinionated code formatter. It does not debate
whether your semicolons belong at line ends or whether object keys should
wrap — it rewrites your source to match a fixed set of layout rules and
moves on. That rigidity is the product: teams stop burning review cycles on
whitespace and start talking about behavior. Prettier supports JavaScript,
TypeScript, JSX, JSON, CSS, HTML, Markdown, YAML, and GraphQL out of the box.
It complements ESLint,
which catches logic bugs and unsafe patterns while Prettier handles
presentation. This guide covers installation, configuration files, ignore
patterns, editor format-on-save workflows, pairing with
eslint-config-prettier, CI and pre-commit integration, monorepo
strategies, a Harbor Commerce worked example, a tooling decision table, common
pitfalls, and a production checklist. Pair it with
TypeScript
and your
Vite
dev pipeline for consistent output from first keystroke to production build.
What Prettier is (and is not)
Prettier parses source into an AST, walks the tree, and prints it back with deterministic whitespace. Given the same input and options, every developer and CI runner produces identical output — no editor-specific quirks.
It is not a linter. Prettier will not flag unused variables,
missing await keywords, or incorrect hook dependencies. It also
does not understand program semantics beyond syntax: it formats broken code
as best it can rather than refusing to run. For correctness checks, use
ESLint and tsc --noEmit alongside formatting.
Core concepts
- Parser — inferred from file extension or set explicitly; each language has a dedicated printer.
- Options — a small, intentional surface (line width, quotes, semicolons, trailing commas) stored in config files.
- Ignore file —
.prettierignoreuses gitignore syntax to skip generated artifacts and vendored code. - Plugin — optional packages for additional languages (e.g. Tailwind class sorting via
prettier-plugin-tailwindcss). - Range format —
--range-start/--range-endfor partial file edits; most teams format whole files instead.
Installation and CLI usage
Install Prettier as a dev dependency so every contributor and CI job uses the same version:
npm install --save-dev prettier
# Format every supported file in the project
npx prettier --write .
# Check without writing (CI gate)
npx prettier --check .
# Single file
npx prettier --write src/App.tsx
Add scripts to package.json so formatting is discoverable:
{
"scripts": {
"format": "prettier --write .",
"format:check": "prettier --check ."
}
}
Prettier respects .gitignore by default when the
--ignore-path flag points at it, but maintain an explicit
.prettierignore for build output (dist/,
.next/, coverage/) and lockfiles you never want
touched. Never format node_modules — it wastes time and
risks corrupting third-party packages if misconfigured.
Configuration: fewer knobs on purpose
Prettier intentionally limits options. Store config in
prettier.config.js (or .prettierrc,
prettier.config.mjs, or a "prettier" key in
package.json). A typical TypeScript + React project:
// prettier.config.js
/** @type {import('prettier').Config} */
export default {
semi: true,
singleQuote: true,
tabWidth: 2,
trailingComma: 'all',
printWidth: 100,
bracketSpacing: true,
arrowParens: 'always',
plugins: ['prettier-plugin-tailwindcss'],
};
Key options and when to change them:
- printWidth — soft wrap target; 80–100 is common. Wider lines reduce vertical scroll in dense UI code.
- singleQuote — JavaScript strings use single quotes; JSX may still use double quotes depending on
jsxSingleQuote. - trailingComma —
"all"produces cleaner git diffs when adding array or object entries. - semi — team preference; pick one and enforce via Prettier, not code review.
- plugins — load language or domain-specific formatters; plugin order matters for Tailwind sorting.
Shareable configs
Publish a company preset as @org/prettier-config and extend it
in each repo:
// prettier.config.js
import orgConfig from '@org/prettier-config';
export default { ...orgConfig, printWidth: 100 };
In monorepos, a root prettier.config.js applies to all packages
unless a subpackage overrides with its own config file. Consistency across
packages matters more than per-package stylistic tweaks.
Pairing Prettier with ESLint
ESLint and Prettier overlap on stylistic rules (quotes, spacing, semicolons). Running both without coordination causes infinite fix loops: ESLint reformats, Prettier reformats back, CI fails mysteriously. The fix is eslint-config-prettier, which disables ESLint rules that conflict with Prettier.
// eslint.config.js
import eslintConfigPrettier from 'eslint-config-prettier';
export default [
// ... your rules and plugins ...
eslintConfigPrettier, // must be last — turns off conflicting stylistic rules
];
Do not use the deprecated eslint-plugin-prettier pattern
that runs Prettier as an ESLint rule unless you have a legacy reason. It is
slower, blurs linter and formatter responsibilities, and makes IDE feedback
harder to parse. Instead: ESLint for bugs, Prettier for layout, two separate
commands in CI.
Recommended CI order:
npm run format:check # prettier --check .
npm run lint # eslint . --max-warnings 0
npm test
Locally, run prettier --write before eslint --fix
in pre-commit hooks so autofix does not fight freshly formatted code.
Editor integration and format on save
The Prettier VS Code extension reads your project config automatically. Enable Format on Save with Prettier as the default formatter for each language you use:
// .vscode/settings.json (committed to the repo)
{
"editor.defaultFormatter": "esbenp.prettier-vscode",
"editor.formatOnSave": true,
"[typescript]": { "editor.defaultFormatter": "esbenp.prettier-vscode" },
"[typescriptreact]": { "editor.defaultFormatter": "esbenp.prettier-vscode" },
"editor.codeActionsOnSave": {
"source.fixAll.eslint": "explicit"
}
}
Committing .vscode/settings.json (or documenting setup in
CONTRIBUTING.md) eliminates “works on my machine” formatting drift.
JetBrains IDEs support Prettier via the official plugin with similar
on-save hooks. For remote dev containers, install Prettier in the container
image so the extension resolves the local node_modules binary.
Formatting non-code assets
Prettier formats package.json, README.md, and
*.yml workflow files. This keeps lock-adjacent metadata
readable and stops YAML indentation bugs in GitHub Actions. Exclude
auto-generated JSON (OpenAPI dumps, large fixture files) via
.prettierignore if formatting them creates noisy diffs.
CI gates and pre-commit hooks
A failing prettier --check in CI is the enforcement backstop
when someone disables format-on-save or edits via a web IDE. GitHub Actions
example:
- name: Check formatting
run: npm run format:check
For faster feedback, wire lint-staged so only changed files format on commit:
// package.json
"lint-staged": {
"*.{js,ts,tsx,json,css,md}": "prettier --write"
}
Pair with Husky: prettier --write runs first, then
eslint --fix on staged paths. In large monorepos, Turborepo
can cache format:check per package when inputs are unchanged.
Fail PRs that mix feature changes with repo-wide format sweeps unless the
format commit is isolated — reviewers cannot find the real diff in a
400-file whitespace PR.
Worked example: Harbor Commerce monorepo
Harbor Commerce runs a React storefront, a Node API, and shared TypeScript types. Before adopting Prettier, code review threads spent 20% of comments on quote style and import line breaks. The team standardized as follows:
- Root config — one
prettier.config.jswithsingleQuote: true,trailingComma: 'all',printWidth: 100, plusprettier-plugin-tailwindcssfor consistent utility class order in JSX. - Ignore list —
dist/,.turbo/,coverage/, generated GraphQL types, andpnpm-lock.yaml. - ESLint pairing — flat config ends with
eslint-config-prettier; removed 14 stylistic rules from the old eslintrc. - Editor defaults — committed
.vscode/settings.jsonso interns and contractors format identically on day one. - CI — Turbo pipeline runs
format:checkandlintin parallel across packages; both must pass before deploy.
The one-time migration PR ran prettier --write . on a dedicated
branch, merged quickly, and added a CI check so formatting never regressed.
Review velocity improved because diffs highlighted logic, not tabs versus
spaces. When Tailwind class order changed after a plugin upgrade, a second
small format PR touched only class strings — easy to scan because
behavioral commits stayed separate.
Tooling decision table
| Need | Reach for | Why |
|---|---|---|
| Opinionated JS/TS/CSS/Markdown formatting | Prettier | Largest ecosystem; editor support; deterministic output |
| Semantic bugs and framework rules | ESLint + plugins | Complements Prettier; never duplicate style rules |
| All-in-one lint + format in Rust | Biome | Faster single binary; smaller community plugin surface |
| Language-native formatting (Go, Rust) | gofmt, rustfmt | Idiomatic per language; do not force Prettier on non-supported stacks |
| Tailwind class ordering in JSX | prettier-plugin-tailwindcss | Official plugin; sort utilities on save |
Common pitfalls
- Running Prettier as an ESLint rule — deprecated pattern; use separate tools and
eslint-config-prettier. - Formatting generated code — add codegen output paths to
.prettierignoreor every regen creates merge noise. - Version skew — pin Prettier in
package.json; a global CLI vs local version produces different output in CI. - Mixing format commits with features — isolate mass-format PRs so reviewers can audit logic changes.
- Forgetting Markdown and YAML — docs and CI configs drift without including
*.mdand*.ymlin format scripts. - Overriding too many options — defeats Prettier’s purpose; pick a preset and move on.
- No CI check — format-on-save alone fails when contributors use vim, web editors, or AI agents that skip the extension.
Production checklist
- Install
prettieras a dev dependency with a pinned version. - Add
formatandformat:checkscripts topackage.json. - Commit
prettier.config.jsand.prettierignoreat the repo root. - Extend
eslint-config-prettieras the last block in ESLint flat config. - Commit
.vscode/settings.jsonwith format-on-save and Prettier as default formatter. - Run
prettier --write .once on a dedicated migration branch before enabling CI. - Gate PRs with
prettier --check .in CI alongside lint and tests. - Wire lint-staged to format staged files before ESLint autofix.
- Add Tailwind or other plugins only when the stack needs them; document plugin order.
- Re-run format after major Prettier or plugin upgrades; read the changelog for breaking default changes.
Key takeaways
- Prettier handles layout deterministically; ESLint handles correctness — keep roles separate.
- eslint-config-prettier prevents rule conflicts; place it last in flat config.
- format:check in CI is non-negotiable for teams larger than one developer.
- .prettierignore protects generated artifacts from noisy diffs.
- Isolate mass-format PRs from feature work so reviews stay focused on behavior.
Related reading
- ESLint fundamentals explained — lint semantics while Prettier handles whitespace
- TypeScript fundamentals explained — types Prettier preserves but does not validate
- Vite fundamentals explained — add format scripts beside dev and build
- Tailwind CSS fundamentals explained — pair with
prettier-plugin-tailwindcssfor class sorting