Sharing ESLint & Prettier config across a monorepo
In a monorepo, each package drifting to its own ESLint and Prettier settings produces inconsistent formatting and noisy diffs. The fix is a single shared config package that every project extends, resolved deterministically inside the DevContainer. This page builds that shared package with ESLint’s flat config and a shared Prettier config, then wires the editor to use them. It extends the Integrating ESLint & Prettier in DevContainers cluster.
Prerequisites
- A monorepo using npm/pnpm/Yarn workspaces
- ESLint 9+ (flat config) and Prettier 3+
- Node.js 20+ in the container
How-To Steps
-
Create a shared config package, e.g.
packages/config-eslint:{ "name": "@org/config-eslint", "version": "1.0.0", "type": "module", "main": "index.js", "peerDependencies": { "eslint": ">=9" } } -
Export a flat config from
packages/config-eslint/index.js:import js from "@eslint/js"; export default [ js.configs.recommended, { rules: { "no-console": "warn", eqeqeq: "error" }, }, ]; -
Consume it in each project’s
eslint.config.js:import shared from "@org/config-eslint"; export default [...shared, { files: ["src/**/*.ts"] }]; -
Share Prettier via a single root config and a
prettierfield pointing at the package:{ "prettier": "@org/config-prettier" } -
Make the editor use the workspace tools in the DevContainer:
{ "image": "mcr.microsoft.com/devcontainers/typescript-node:20@sha256:7c3e...", "postCreateCommand": "pnpm install --frozen-lockfile", "customizations": { "vscode": { "extensions": ["dbaeumer.vscode-eslint", "esbenp.prettier-vscode"], "settings": { "editor.defaultFormatter": "esbenp.prettier-vscode", "editor.formatOnSave": true, "eslint.useFlatConfig": true } } }, "remoteUser": "vscode" }Verify across packages:
pnpm -r exec eslint . --max-warnings=0
Common Pitfalls
| Symptom | Root Cause | Remediation |
|---|---|---|
| Each package lints differently | Local configs override the shared one | Extend the shared package; delete per-package rule drift |
@org/config-eslint not found | Workspace package not installed/symlinked | Run the workspace install in postCreateCommand |
| Editor uses legacy config | eslint.useFlatConfig unset on ESLint 9 | Enable flat config in settings |
| Prettier and ESLint fight | Overlapping formatting rules | Use eslint-config-prettier to disable conflicting rules |
Conclusion
The invariant: one shared, versioned config package is the single source of truth, and every project extends it rather than redefining rules. Resolve it through the workspace installer in postCreateCommand and the entire monorepo lints and formats identically inside every DevContainer.
FAQ
Flat config or legacy .eslintrc?
Use flat config on ESLint 9+. It composes as plain arrays, which is exactly what a shared package needs to export and consumers to spread.
How do I stop ESLint and Prettier from conflicting?
Add eslint-config-prettier to the end of your shared flat config so it turns off ESLint’s formatting rules, leaving formatting to Prettier.
Related
- Integrating ESLint & Prettier in DevContainers — the parent cluster.
- Running pre-commit hooks on postCreateCommand with Husky — enforcing these linters at commit time.
- Using pnpm workspaces in a DevContainer — the workspace layout this config sits in.