Claude Code with Prettier: Config, ESLint Integration, Format-on-Save
Why Prettier needs explicit CLAUDE.md rules
Prettier has one job: format code the same way every time. That consistency is the whole value proposition. The problem is that Claude Code does not read your .prettierrc automatically. It reads whatever formatting conventions it saw most during training, which defaults to two-space indentation, double quotes, trailing commas everywhere, and an 80-character print width. If your project uses 120-character lines, single quotes, and no trailing commas, every suggestion Claude generates needs a reformat pass before it passes your pre-commit hook.
That friction compounds across a working session. You ask Claude to scaffold a component. It comes back formatted differently from the rest of the file. You run Prettier. You paste it back. You ask for a test. Same pattern. By the end of a feature the reformatting is automatic, which is exactly the kind of invisible tax that erodes the speed benefit of using an AI assistant in the first place.
The fix is a CLAUDE.md that tells Claude your exact Prettier config so it generates code that matches on the first write. This guide covers the full setup: reading your existing .prettierrc, the ESLint integration rules that prevent conflicts, file-type overrides, semicolon and line-length decisions, and the husky pre-commit workflow that enforces consistency for the whole team.
If you are new to the CLAUDE.md system, CLAUDE.md explained covers the basics. For the broader TypeScript conventions that compose with Prettier config, Claude Code with TypeScript is worth reading alongside this guide.
The Prettier CLAUDE.md template
The CLAUDE.md at your project root is read at the start of every Claude Code session. For Prettier it needs to declare: Prettier version, the canonical config file location, every non-default setting you have chosen, the ESLint relationship, file-type overrides, and the hard rules about what Claude should not do with formatting.
# Prettier formatting rules
## Stack
- Prettier 3.x (not 2.x, check API and config key differences)
- eslint-config-prettier 9.x (disables conflicting ESLint rules)
- husky 9.x + lint-staged 15.x for pre-commit enforcement
## Config file
- Config lives at .prettierrc.json (single source of truth)
- Do not inline formatting options in comments or code
- Do not regenerate .prettierrc.json without reading the current version first
## Current .prettierrc.json settings
{
"printWidth": 120,
"tabWidth": 2,
"useTabs": false,
"semi": true,
"singleQuote": true,
"quoteProps": "as-needed",
"jsxSingleQuote": false,
"trailingComma": "all",
"bracketSpacing": true,
"bracketSameLine": false,
"arrowParens": "always",
"endOfLine": "lf"
}
## File-type overrides (from .prettierrc.json overrides array)
- JSON/YAML: printWidth 80 (readability in diffs)
- Markdown: proseWrap "always", printWidth 80
- CSS/SCSS: singleQuote false
## Hard rules
- NEVER change printWidth when asked to keep lines shorter. Break the expression instead
- NEVER mix single and double quotes within a file
- NEVER add trailing commas to function parameters in .ts files that predate ES2017 targets
- ALL generated code must pass: npx prettier --check <file>
- Do not suggest disabling Prettier for a block with prettier-ignore unless the block is machine-generated (e.g., an inlined SVG path or a codegen output section)
Two things in this template earn their place more than the others.
The version declaration matters because Prettier 3 changed several defaults from Prettier 2. The trailingComma default moved to "all" in Prettier 3, which means a project that ran Prettier 2 with no explicit trailingComma setting will start reformatting function-parameter trailing commas the moment it upgrades. Claude can cross Prettier 2 and Prettier 3 patterns in its suggestions if it does not know which version you are on. Declaring 3.x anchors it.
The inline config copy is the most practically useful part of the whole template. Instead of telling Claude "read my .prettierrc", paste the actual JSON. This avoids file-read overhead at the start of every session and guarantees Claude is matching the exact values in your config, not an approximation. Update this section whenever you change .prettierrc.json.
The ESLint integration: eslint-config-prettier and the flat config migration
Prettier and ESLint cover different ground. ESLint catches bugs, enforces patterns, and flags problematic code. Prettier formats. The problem is that ESLint also ships formatting-related rules (indent, quotes, semi, max-len) that directly conflict with what Prettier does. Without the right setup, ESLint and Prettier fight: Prettier reformats a line, ESLint flags it, Claude tries to satisfy both and ends up satisfying neither.
The standard fix is eslint-config-prettier, which turns off all ESLint rules that are unnecessary or conflict with Prettier. This is the configuration Claude should generate in every project that uses both tools.
For projects using the legacy .eslintrc format:
{
"extends": [
"eslint:recommended",
"plugin:@typescript-eslint/recommended",
"prettier"
]
}
The "prettier" entry must come last. It overrides any formatting-related rules set by configs that precede it. Claude sometimes puts it in the middle of the extends array, which means a subsequent config re-enables the conflicting rules. Put it last, always.
For projects that have migrated to ESLint's flat config (eslint.config.js), which is now the default in ESLint 9.x:
import eslint from '@eslint/js';
import tseslint from 'typescript-eslint';
import prettierConfig from 'eslint-config-prettier';
export default tseslint.config(
eslint.configs.recommended,
...tseslint.configs.recommended,
prettierConfig,
);
The flat config import is eslint-config-prettier directly (not a plugin). Spread position still matters: prettierConfig goes at the end of the array so it wins any rule conflicts.
Add an ESLint section to your CLAUDE.md:
## ESLint + Prettier relationship
- eslint-config-prettier disables formatting rules that conflict with Prettier
- "prettier" (legacy config) or prettierConfig (flat config) ALWAYS goes last in extends/config array
- eslint-plugin-prettier is NOT used in this project (it runs Prettier as an ESLint rule, which is slower and produces duplicate error messages; use a separate Prettier pass instead)
- Correct responsibility split:
- ESLint: bugs, patterns, type safety, import order
- Prettier: all whitespace, quotes, semicolons, line breaks
- NEVER fix a Prettier-owned formatting issue by adding an ESLint rule
The note about eslint-plugin-prettier is worth emphasising. This plugin runs Prettier as an ESLint rule and marks formatting violations as lint errors. The Prettier team now explicitly recommends against it because it is slower than running Prettier separately, produces duplicate errors (once from Prettier, once from ESLint), and couples two tools that are better kept independent. Claude will sometimes add eslint-plugin-prettier because older tutorials show it. The rule keeps it out.
For the broader TypeScript ESLint patterns that sit alongside this config, Claude Code with Next.js covers the Next.js-specific ESLint setup that uses this same split.
Line length, semicolons, and trailing commas: the three decisions
These three settings account for the majority of Prettier disagreements across projects. Claude defaults to each differently depending on which codebase appeared most in its training data. Declaring them explicitly in CLAUDE.md eliminates the ambiguity.
Print width
Prettier's printWidth is a hint, not a hard limit. Prettier will not break a string literal to fit within printWidth, and it will not break a function call if it fits on one line. The setting influences where Prettier decides to break multi-element arrays, function argument lists, and JSX attribute groups.
The practical decision is usually 80 vs 100 vs 120:
## Line length decision guide
- 80: Default, maximises side-by-side diff readability, works in narrow terminals
- 100: Common in React/TypeScript projects with long JSX attribute chains
- 120: Common in Node/backend projects where JSX is absent and function signatures are longer
- 140+: Uncommon, usually a sign the codebase is avoiding expression-breaking rather than choosing a convention
### This project: printWidth 120
- Reason: server-side TypeScript, no JSX, long function signatures from ORM queries
- When Claude generates a line over 120 chars: break at the last argument, not in the middle of a string
The annotation of which print width your project uses and why helps Claude choose the right break strategy. At 120 characters, a Drizzle ORM query with several chained .where() calls reads better when Prettier breaks after each method. At 80 characters, the same query breaks inside the method arguments in ways that can obscure the query structure.
Semicolons
The semi setting is binary. Most projects are consistent about it from the start, so this is less a design choice than a declaration:
## Semicolons
- semi: true (this project always uses semicolons)
- NEVER omit semicolons on new code, even when the statement is the last in a block
- NEVER add ASI-reliant patterns (e.g., lines starting with [ or () without a semicolon guard)
The ASI rule is the subtle one. In a project with semi: false, a line starting with [ is legal JavaScript but reads as array access on the previous expression. Claude will sometimes produce ASI-safe code in semi: true projects because it hedges. If your project has semi: true, explicitly ruling out ASI-reliant patterns removes that hedging.
Trailing commas
Prettier 3 defaults to "all", which adds trailing commas to function parameter lists in addition to arrays and objects. This is a source of merge conflicts in legacy projects that upgraded from Prettier 2 without updating their trailingComma setting. Add the current value and the historical reason if relevant:
## Trailing commas
- trailingComma: "all" (Prettier 3 default, includes function parameters)
- If you see a PR comment about trailing commas on function params, it predates the Prettier 3 upgrade. The new setting is intentional
- ES5 target compatibility note: trailing commas in function params require ES2017+. This project targets ES2020, so "all" is safe.
File-type overrides and the overrides array
Prettier lets you apply different formatting rules per file glob through the overrides array in .prettierrc.json. These overrides are the most commonly forgotten configuration detail, because they live in the config file but are invisible in the output of most editor integrations.
A typical set of overrides for a Next.js or Node project:
{
"printWidth": 120,
"singleQuote": true,
"semi": true,
"trailingComma": "all",
"overrides": [
{
"files": ["*.json", "*.jsonc"],
"options": {
"printWidth": 80,
"trailingComma": "none"
}
},
{
"files": ["*.md", "*.mdx"],
"options": {
"proseWrap": "always",
"printWidth": 80
}
},
{
"files": ["*.yaml", "*.yml"],
"options": {
"printWidth": 80,
"singleQuote": false
}
},
{
"files": ["*.css", "*.scss"],
"options": {
"singleQuote": false
}
}
]
}
The rationale for each:
JSON files use printWidth: 80 because JSON diffs are read in PR review tools with narrow panels. A 120-character JSON line wraps awkwardly in GitHub's diff view. trailingComma: "none" is set explicitly because JSON5 (.jsonc) supports trailing commas but standard JSON does not, and Prettier will add them to .json files if you set "all" globally.
Markdown uses proseWrap: "always" because Prettier will hard-wrap prose paragraphs at printWidth. Without this, Markdown files stay as single long lines regardless of printWidth, which makes reading raw Markdown uncomfortable. printWidth: 80 keeps Markdown readable in any terminal.
YAML uses singleQuote: false because YAML strings with single quotes behave differently from double-quoted strings (no escape sequences in single-quoted scalars), and Prettier defaulting to single quotes can change the semantics of YAML values that contain backslashes.
Add the overrides to your CLAUDE.md:
## File-type overrides
- JSON/JSONC: printWidth 80, trailingComma none
- Markdown/MDX: proseWrap always, printWidth 80
- YAML/YML: printWidth 80, singleQuote false
- CSS/SCSS: singleQuote false
- Do NOT apply JS/TS formatting conventions to these file types
- When modifying any of these file types, run: npx prettier --write <file> before suggesting further edits
The last rule is important. Claude will sometimes edit a YAML or Markdown file using the project's global Prettier settings instead of the overrides, producing output that does not match what prettier --write would generate. The explicit instruction to run prettier --write on the file before further edits catches this.
For monorepos where different packages have different Prettier configs, the rules in Claude Code with monorepos cover the workspace-level CLAUDE.md structure that handles per-package formatting conventions.
Husky and lint-staged: the pre-commit hook Claude must respect
Husky adds Git hooks to your project. Lint-staged runs formatters and linters against staged files only, which keeps the pre-commit hook fast even in large codebases. Together they enforce Prettier on every commit so that formatted code is the only code that ever enters your repository.
The setup for Prettier 3.x with Husky 9.x and lint-staged 15.x:
pnpm add -D prettier@^3 eslint-config-prettier@^9 husky@^9 lint-staged@^15
pnpm exec husky init
husky init creates .husky/pre-commit and adds a prepare script to package.json. Edit .husky/pre-commit:
#!/usr/bin/env sh
. "$(dirname "$0")/_/husky.sh"
pnpm exec lint-staged
Add a lint-staged key to package.json:
{
"lint-staged": {
"*.{js,jsx,ts,tsx}": [
"eslint --fix",
"prettier --write"
],
"*.{json,jsonc,md,mdx,yaml,yml,css,scss}": [
"prettier --write"
]
}
}
The ESLint --fix runs before Prettier so that any auto-fixable ESLint issues are resolved first, then Prettier does a final formatting pass. Running them in the opposite order means Prettier's formatting may trigger ESLint rules that then re-format the file again.
Add a pre-commit section to CLAUDE.md:
## Pre-commit hooks (husky + lint-staged)
- .husky/pre-commit runs lint-staged on every commit
- lint-staged runs eslint --fix then prettier --write on staged JS/TS files
- lint-staged runs prettier --write on JSON, YAML, Markdown, CSS files
- NEVER suggest bypassing hooks with git commit --no-verify unless the file is explicitly a generated artifact
- NEVER suggest committing unformatted files "to fix later"
- If a commit fails the pre-commit hook: read the lint-staged output, fix the issue, re-stage, then commit
- Generated files in src/generated/ are excluded from lint-staged via .prettierignore
The --no-verify rule matters. Claude will occasionally suggest git commit --no-verify when a pre-commit hook is blocking a commit it has generated. That circumvents the whole system. The hook failure is information: either the code does not match the project's Prettier config, or there is a lint error. Both are worth fixing. For the broader Git workflow patterns this integrates with, Claude Code git workflow covers the commit and branch conventions that sit alongside these hooks.
The PostToolUse hook: format-on-generate inside Claude Code
Beyond the pre-commit hook enforced by husky, you can configure Claude Code itself to run Prettier immediately after every file it writes or edits. This creates a tight loop where every output is formatted before you even see it.
In .claude/settings.json:
{
"hooks": {
"PostToolUse": [
{
"matcher": "Edit|Write",
"hooks": [
{
"type": "command",
"command": "npx prettier --write \"$CLAUDE_FILE_PATH\" 2>/dev/null || true"
}
]
}
]
}
}
The || true prevents the hook from blocking Claude Code if Prettier errors on a file type it does not support (binary files, lock files). The 2>/dev/null suppresses Prettier's stderr on unsupported files.
For TypeScript-heavy projects where you also want type checking after each write, chain the commands:
{
"hooks": {
"PostToolUse": [
{
"matcher": "Edit|Write",
"hooks": [
{
"type": "command",
"command": "npx prettier --write \"$CLAUDE_FILE_PATH\" 2>/dev/null || true"
},
{
"type": "command",
"command": "npx tsc --noEmit --skipLibCheck 2>/dev/null || true"
}
]
}
]
}
}
Add a hooks section to CLAUDE.md:
## Claude Code PostToolUse hook
- .claude/settings.json configures a PostToolUse hook that runs prettier --write on every file Claude edits
- This means Claude-generated code is formatted before it is shown to you
- Do NOT add manual prettier --write calls inside code suggestions. The hook handles it
- Hook runs on Edit and Write tool uses; does not run on Bash output
- If the hook fails silently (|| true), run npx prettier --check <file> to surface the issue
This approach pairs well with the vitest workflow covered in Claude Code with Vitest, where a similar PostToolUse hook runs tests after each file write and gives Claude immediate feedback on whether the generated code passes.
Common failure modes Claude produces without Prettier context
These are the specific patterns that appear repeatedly when Claude generates code in a Prettier project without explicit CLAUDE.md rules.
Wrong quote style. Claude defaults to double quotes in TypeScript files. If your .prettierrc.json sets "singleQuote": true, every string literal Claude generates needs reformatting. This is the highest-frequency issue, appearing in most TypeScript files Claude touches. The fix is a single CLAUDE.md rule: "singleQuote: true, use single quotes in all JS/TS files."
Trailing comma placement. In Prettier 2 projects using "trailingComma": "es5", function parameter lists do not get trailing commas. Claude, trained on Prettier 3 conventions, adds them. In strict TypeScript projects targeting older environments, this is a compile error, not just a style disagreement. Declare the exact trailingComma value and the TypeScript target.
80-character wrapping in a 120-character project. Claude breaks long lines at 80 characters because that is the most common printWidth in the codebases it has seen. In a 120-character project, this produces fragmented code that Prettier immediately rejoins when you run prettier --write. The multi-line function calls look deliberate but are wrong. Stating printWidth: 120 in CLAUDE.md eliminates this.
Ignoring file-type overrides. Claude applies JS/TS conventions to YAML and Markdown files. Single quotes in YAML values change their semantics. Hard-wrapped Markdown prose appears when proseWrap: "never" is the project setting. Always include the overrides section in CLAUDE.md and add the prettier --write instruction for non-JS file types.
Adding prettier-ignore liberally. When Claude cannot format a code block to its satisfaction, it sometimes adds // prettier-ignore above the block. This is almost always wrong. prettier-ignore is appropriate for machine-generated output or inlined binary data, not for code that Prettier should format but Claude chose not to. The hard rule against prettier-ignore on non-generated code keeps this out of the codebase.
Editing .prettierignore to exclude files. Claude will occasionally add files to .prettierignore when it generates code that does not pass prettier --check. The correct response is to fix the code, not to exclude it from formatting. Add a rule to CLAUDE.md: "Do NOT add entries to .prettierignore without explicit instruction."
For the React-specific formatting considerations (JSX attribute line length, JSX single quote, bracket same line), Claude Code with React covers the JSX-specific Prettier settings that sit on top of this base config.
The complete Prettier CLAUDE.md
Combining everything above into a single CLAUDE.md section you can paste directly:
# Prettier formatting rules
## Stack
- Prettier 3.x
- eslint-config-prettier 9.x (flat config, prettierConfig goes last in array)
- husky 9.x + lint-staged 15.x
- PostToolUse hook runs prettier --write after every Claude edit
## Config (from .prettierrc.json, do not override without instruction)
- printWidth: 120
- tabWidth: 2
- useTabs: false
- semi: true
- singleQuote: true
- jsxSingleQuote: false
- trailingComma: "all"
- bracketSpacing: true
- bracketSameLine: false
- arrowParens: "always"
- endOfLine: "lf"
## File-type overrides
- JSON/JSONC: printWidth 80, trailingComma none
- Markdown/MDX: proseWrap always, printWidth 80
- YAML/YML: printWidth 80, singleQuote false
- CSS/SCSS: singleQuote false
## ESLint relationship
- eslint-config-prettier disables conflicting rules
- eslint-plugin-prettier is NOT used
- ESLint: bugs, patterns, types. Prettier: all formatting.
- prettierConfig always goes last in eslint.config.js
## Pre-commit hooks
- git commit runs lint-staged: eslint --fix then prettier --write on staged JS/TS
- prettier --write runs on staged JSON, YAML, Markdown, CSS
- NEVER use --no-verify to bypass hooks
- NEVER add to .prettierignore without explicit instruction
- NEVER add prettier-ignore comments on non-generated code
## Hard rules
- Use single quotes in JS/TS
- printWidth is 120, do not wrap at 80
- trailingComma "all" applies to function parameters
- Run: npx prettier --check <file> to verify any generated file
Keeping Claude-generated code consistently formatted
The CLAUDE.md in this guide produces three layers of enforcement: Claude generates code matching your Prettier config on the first write, the PostToolUse hook reformats immediately after each file edit, and the husky pre-commit hook catches anything that slipped through before it enters your repository.
The order of setup matters. The CLAUDE.md settings come first because they change what Claude generates before the hooks even run. The PostToolUse hook is a safety net for the gap between what Claude intended and what Prettier expects. The pre-commit hook is the final guarantee that formatted code is the only code in your commit history.
For teams using Prettier across a monorepo with different per-package configs, the workspace-level CLAUDE.md patterns in Claude Code with monorepos extend this setup with package-scoped overrides and a root-level prettier.config.js that packages can inherit from. For the TypeScript compiler settings that combine with Prettier in a strict-mode project, Claude Code with TypeScript covers the tsconfig conventions that pair with the target-aware trailing comma rules above.
Claudify includes a Prettier-ready CLAUDE.md template preconfigured for Prettier 3.x, eslint-config-prettier flat config, and the husky+lint-staged pre-commit setup.
More like this
Ready to upgrade your Claude Code setup?
Get Claudify