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

  1. 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" }
    }
    
  2. 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" },
      },
    ];
    
  3. Consume it in each project’s eslint.config.js:

    import shared from "@org/config-eslint";
    export default [...shared, { files: ["src/**/*.ts"] }];
    
  4. Share Prettier via a single root config and a prettier field pointing at the package:

    { "prettier": "@org/config-prettier" }
    
  5. 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

SymptomRoot CauseRemediation
Each package lints differentlyLocal configs override the shared oneExtend the shared package; delete per-package rule drift
@org/config-eslint not foundWorkspace package not installed/symlinkedRun the workspace install in postCreateCommand
Editor uses legacy configeslint.useFlatConfig unset on ESLint 9Enable flat config in settings
Prettier and ESLint fightOverlapping formatting rulesUse 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.