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.prettierignore uses 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-end for 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:

  1. Root config — one prettier.config.js with singleQuote: true, trailingComma: 'all', printWidth: 100, plus prettier-plugin-tailwindcss for consistent utility class order in JSX.
  2. Ignore listdist/, .turbo/, coverage/, generated GraphQL types, and pnpm-lock.yaml.
  3. ESLint pairing — flat config ends with eslint-config-prettier; removed 14 stylistic rules from the old eslintrc.
  4. Editor defaults — committed .vscode/settings.json so interns and contractors format identically on day one.
  5. CI — Turbo pipeline runs format:check and lint in 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

NeedReach forWhy
Opinionated JS/TS/CSS/Markdown formattingPrettierLargest ecosystem; editor support; deterministic output
Semantic bugs and framework rulesESLint + pluginsComplements Prettier; never duplicate style rules
All-in-one lint + format in RustBiomeFaster single binary; smaller community plugin surface
Language-native formatting (Go, Rust)gofmt, rustfmtIdiomatic per language; do not force Prettier on non-supported stacks
Tailwind class ordering in JSXprettier-plugin-tailwindcssOfficial 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 .prettierignore or 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 *.md and *.yml in 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 prettier as a dev dependency with a pinned version.
  • Add format and format:check scripts to package.json.
  • Commit prettier.config.js and .prettierignore at the repo root.
  • Extend eslint-config-prettier as the last block in ESLint flat config.
  • Commit .vscode/settings.json with 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