| | |

Prettier 29 ๐ŸŽจ Monorepo Configuration โ€” Shared configs & per-package overrides

โฑ๏ธ Estimated read: 11 min | |

A monorepo’s Prettier configuration has two layers: the shared config that every package should inherit, and the per-package overrides that a specific package needs. The mechanism is Prettier’s configuration file resolution, which searches upward from the file being formatted until it finds a config. A package-level config stops the search, so the shared config at the root is ignored for that package unless the package-level config explicitly extends it. The shared config is the baseline; the per-package config is the exception.

The first decision is whether the shared config is a workspace package or a root-level file. A workspace package (@myorg/prettier-config) is installable, versionable, and publishable, and it is the pattern that the Prettier documentation recommends for a config that is shared across projects . A root-level .prettierrc is simpler, but it is not installable, and a package that needs to extend it must reach up the directory tree or duplicate the settings. The workspace package is the more maintainable choice for a monorepo with more than a few packages.

Key point: The shared config is a workspace package that exports a Prettier config object. A package that wants the shared settings references it with the "prettier" key in package.json or a local config file. A package that needs overrides imports the shared config and spreads it into a new object with the overrides. The per-package config stops the upward search, so the shared config is only used when the package explicitly references it.


Why the shared config is a workspace package

The installability problem. A root-level .prettierrc is a file, not a package. A package that wants to extend it must use a relative path (../../.prettierrc), which breaks when the package is moved or when the config is refactored. A workspace package is installed into node_modules, and the package-level config can reference it by name (@myorg/prettier-config), which is stable regardless of the directory structure .

The plugin problem. A shared config that uses pluginsโ€”prettier-plugin-tailwindcss, @ianvs/prettier-plugin-sort-importsโ€”must declare the plugins as dependencies of the config package. The Prettier documentation states that a shareable config should declare the plugins in the plugins array and as dependencies in its package.json . This is the mechanism that ensures the plugins are installed when the config is installed, regardless of which package consumes the config.

The version problem. A workspace package has a version. When the shared config changes, the change is a new version of the package, and the packages that depend on it can be updated deliberately. A root-level file has no version, and a change to the file affects every package at once. The versioning is the mechanism for a gradual rollout of a config change.

The trade-off. A workspace package adds a package to the monorepo. It has a package.json, an index.js, and a version. The overhead is small, and the benefit is the installability, the plugin declaration, and the versioning. A root-level file is simpler for a monorepo with one or two packages, but the workspace package is the pattern that scales.


a. The shared config package

The shared config package exports a Prettier config object. The index.js file is the config, and the package.json declares the name, the entry point, and the plugin dependencies.

// packages/prettier-config/index.js โ€” the shared config
/** @type {import("prettier").Config} */
const config = {
  semi: true,
  singleQuote: true,
  trailingComma: 'all',
  printWidth: 100,
  plugins: [
    '@ianvs/prettier-plugin-sort-imports',
    'prettier-plugin-tailwindcss',
  ],
  importOrder: ['<BUILTIN_MODULES>', '<THIRD_PARTY_MODULES>', '^[.]'],
};

export default config;
// packages/prettier-config/package.json โ€” the shared config package
{
  "name": "@myorg/prettier-config",
  "version": "1.0.0",
  "type": "module",
  "exports": "./index.js",
  "peerDependencies": {
    "prettier": ">=3.0.0"
  },
  "dependencies": {
    "@ianvs/prettier-plugin-sort-imports": "^4.0.0",
    "prettier-plugin-tailwindcss": "^0.6.0"
  }
}

The peerDependencies declares Prettier as a peer, which means the consuming package must have Prettier installed. The dependencies declares the plugins, which means they are installed with the config package. The Prettier documentation recommends this split: Prettier is a peer because the consumer already has it, and the plugins are dependencies because the config is the only thing that needs them .


b. The per-package reference and the override pattern

A package that wants the shared settings references the config package. The simplest form is the "prettier" key in package.json.

// apps/web/package.json โ€” the shared config reference
{
  "name": "@myorg/web",
  "prettier": "@myorg/prettier-config"
}

A package that needs overrides imports the shared config and spreads it into a new object with the overrides. The spread is the mechanism that keeps the shared settings and adds the package-specific ones.

// apps/web/prettier.config.mjs โ€” the shared config with overrides
import sharedConfig from '@myorg/prettier-config';

/** @type {import("prettier").Config} */
export default {
  ...sharedConfig,
  // The package-specific overrides.
  printWidth: 80,
  // The package-specific plugin overrides.
  plugins: [...sharedConfig.plugins, 'prettier-plugin-svelte'],
};

The spread is shallow, which means the plugins array is replaced, not merged. The package that adds a plugin must spread the shared array and append the new plugin, as the example does. The importOrder array is also replaced, which means a package that changes the order must include the full order it wants.

The per-package config stops the upward search. If the package has a prettier.config.mjs, Prettier uses it and does not look for a root-level .prettierrc. The shared config is only used because the package explicitly imports it. This is the mechanism that makes the per-package override independent of the root configuration .


c. The pnpm plugin resolution workaround

A known issue in pnpm monorepos affects a package-level config that references a plugin. Prettier resolves the plugin relative to the working directory where Prettier is run, not relative to the config file that references it. If the plugin is installed in the package but Prettier is run from the workspace root, the resolution fails .

The workaround is to add the plugin to the publicHoistPattern in the pnpm workspace configuration, which hoists the plugin to the root node_modules where the resolution can find it.

# pnpm-workspace.yaml โ€” the plugin hoisting workaround
publicHoistPattern:
  - '*prettier*'

The workaround is a pnpm-specific fix. The npm and yarn workspaces hoist the packages differently, and the issue does not appear in the same form. A pnpm monorepo that uses a shared config with plugins should add the pattern to the workspace configuration .


Complete Example Session

// ============================================
// PART 1: THE SHARED CONFIG PACKAGE
// ============================================

// packages/prettier-config/index.js
/** @type {import("prettier").Config} */
const config = {
  semi: true,
  singleQuote: true,
  trailingComma: 'all',
  printWidth: 100,
  plugins: ['@ianvs/prettier-plugin-sort-imports'],
  importOrder: ['<BUILTIN_MODULES>', '<THIRD_PARTY_MODULES>', '^[.]'],
};

export default config;

// ============================================
// PART 2: THE SHARED CONFIG PACKAGE.JSON
// ============================================

// packages/prettier-config/package.json
{
  "name": "@myorg/prettier-config",
  "version": "1.0.0",
  "type": "module",
  "exports": "./index.js",
  "peerDependencies": { "prettier": ">=3.0.0" },
  "dependencies": { "@ianvs/prettier-plugin-sort-imports": "^4.0.0" }
}

// ============================================
// PART 3: THE PACKAGE REFERENCE
// ============================================

// apps/web/package.json
{
  "name": "@myorg/web",
  "prettier": "@myorg/prettier-config"
}

// The package uses the shared config without a local file.

// ============================================
// PART 4: THE PACKAGE OVERRIDE
// ============================================

// apps/web/prettier.config.mjs
import sharedConfig from '@myorg/prettier-config';

export default {
  ...sharedConfig,
  printWidth: 80,
};

// The package uses the shared config and overrides one option.

// ============================================
// PART 5: THE PLUGIN OVERRIDE
// ============================================

// apps/web/prettier.config.mjs
import sharedConfig from '@myorg/prettier-config';

export default {
  ...sharedConfig,
  plugins: [...sharedConfig.plugins, 'prettier-plugin-svelte'],
};

// The plugins array is replaced, so the shared plugins
// must be spread into the new array.

// ============================================
// PART 6: THE PNPM WORKAROUND
// ============================================

// pnpm-workspace.yaml
publicHoistPattern:
  - '*prettier*'

// The plugin is hoisted to the root node_modules.
// The resolution works when Prettier is run from the root.

// ============================================
// PART 7: THE ROOT SCRIPT
// ============================================

// package.json (root)
{
  "scripts": {
    "format": "prettier --write .",
    "format:check": "prettier --check ."
  }
}

// The root script runs Prettier on the whole monorepo.
// The per-package configs are resolved per file.

// ============================================
// PART 8: THE SHARED CONFIG VERSIONING
// ============================================

// The shared config is a workspace package.
// A change to the config is a version bump.
// The packages that depend on it are updated deliberately.

// ============================================
// PART 9: THE VERIFICATION
// ============================================

// 1. Run the root format script.
// 2. Check that the shared settings apply to the packages
//    that reference the shared config.
// 3. Check that the overrides apply to the package
//    that has the local config.
// 4. Check that the plugins load.

// ============================================
// PART 10: THE CHECKLIST
// ============================================

// [ ] The shared config is a workspace package.
// [ ] The shared config declares Prettier as a peer dependency.
// [ ] The shared config declares the plugins as dependencies.
// [ ] The packages reference the shared config by name.
// [ ] The packages that need overrides import and spread the shared config.
// [ ] The plugins array is spread when a package adds a plugin.
// [ ] The pnpm workaround is applied if the project uses pnpm.

The ten parts covered the shared config package, the package.json, the package reference, the package override, the plugin override, the pnpm workaround, the root script, the versioning, the verification, and the checklist. The through-line is that the shared config is a package, and the per-package config is an explicit import.


Quick Reference

The shared config package

FilePurpose
index.jsThe config object
package.jsonThe name, entry, and plugin dependencies

The per-package reference

FormFileUse case
"prettier": "@myorg/prettier-config"package.jsonNo overrides
prettier.config.mjs with spreadLocal fileOverrides

The pnpm workaround

SettingLocation
publicHoistPattern: ['*prettier*']pnpm-workspace.yaml

The plugin declaration

DependencyTypeWhy
prettierpeerDependenciesThe consumer has it
The pluginsdependenciesThe config needs them

Best Practices

โœ… Do This:

// Use a workspace package for the shared config
export default { semi: true, singleQuote: true };                 // โœ…
// Reference the shared config by name
"prettier": "@myorg/prettier-config"                              // โœ…
// Spread the shared config for overrides
export default { ...sharedConfig, printWidth: 80 };               // โœ…

โŒ Don’t Do This:

// Use a relative path to the root config
export default { ...require('../../.prettierrc') };               // โŒ
// Replace the plugins array without spreading
plugins: ['my-plugin'] // the shared plugins are gone             // โŒ

Common Pitfalls

PitfallWhy It HappensFix
The shared config is not foundThe package is not installedAdd the workspace dependency
The plugins do not loadThe config package does not declare themAdd them to dependencies
The plugins do not resolve in pnpmThe plugin is not hoistedAdd publicHoistPattern
The overrides do not applyThe local config does not import the shared configUse the spread
The shared plugins are missingThe plugins array is replacedSpread sharedConfig.plugins

Real-World Examples

1. The shared config

// packages/prettier-config/index.js
export default { semi: true, singleQuote: true };

2. The shared package

{ "name": "@myorg/prettier-config", "exports": "./index.js" }

3. The package reference

{ "prettier": "@myorg/prettier-config" }

4. The package override

export default { ...sharedConfig, printWidth: 80 };

5. The plugin override

export default { ...sharedConfig, plugins: [...sharedConfig.plugins, 'prettier-plugin-svelte'] };

6. The pnpm workaround

publicHoistPattern: ['*prettier*']

7. The peer dependency

{ "peerDependencies": { "prettier": ">=3.0.0" } }

8. The plugin dependency

{ "dependencies": { "@ianvs/prettier-plugin-sort-imports": "^4.0.0" } }

9. The root script

{ "format": "prettier --write ." }

10. The verification

npm run format:check

Visual

The Monorepo Structure

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  THE SHARED CONFIG AND THE PACKAGES                           โ”‚
โ”‚                                                              โ”‚
โ”‚  packages/prettier-config/                                   โ”‚
โ”‚  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”                                    โ”‚
โ”‚  โ”‚ index.js            โ”‚  The shared config                 โ”‚
โ”‚  โ”‚ package.json        โ”‚  The name and plugins              โ”‚
โ”‚  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜                                    โ”‚
โ”‚             โ”‚                                                โ”‚
โ”‚             โ”‚ referenced by                                  โ”‚
โ”‚             โ–ผ                                                    โ”‚
โ”‚  apps/web/                    apps/api/                      โ”‚
โ”‚  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”     โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”        โ”‚
โ”‚  โ”‚ package.json:       โ”‚     โ”‚ package.json:       โ”‚        โ”‚
โ”‚  โ”‚   "prettier":       โ”‚     โ”‚   "prettier":       โ”‚        โ”‚
โ”‚  โ”‚   "@myorg/prettier" โ”‚     โ”‚   "@myorg/prettier" โ”‚        โ”‚
โ”‚  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜     โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜        โ”‚
โ”‚                                                              โ”‚
โ”‚  The packages reference the shared config by name.          โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

The Override Pattern

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  THE SPREAD KEEPS THE SHARED SETTINGS                         โ”‚
โ”‚                                                              โ”‚
โ”‚  import sharedConfig from '@myorg/prettier-config';         โ”‚
โ”‚                                                              โ”‚
โ”‚  export default {                                            โ”‚
โ”‚    ...sharedConfig,   โ† the shared settings                 โ”‚
โ”‚    printWidth: 80,    โ† the override                        โ”‚
โ”‚  };                                                          โ”‚
โ”‚                                                              โ”‚
โ”‚  The spread is shallow, so the plugins array is replaced.   โ”‚
โ”‚  The package that adds a plugin must spread the shared array.โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

The Plugin Declaration

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  PEER VS DEPENDENCY                                           โ”‚
โ”‚                                                              โ”‚
โ”‚  peerDependencies:                                           โ”‚
โ”‚  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”                                    โ”‚
โ”‚  โ”‚ prettier            โ”‚  The consumer has it               โ”‚
โ”‚  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜                                    โ”‚
โ”‚                                                              โ”‚
โ”‚  dependencies:                                               โ”‚
โ”‚  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”                                    โ”‚
โ”‚  โ”‚ the plugins         โ”‚  The config needs them             โ”‚
โ”‚  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜                                    โ”‚
โ”‚                                                              โ”‚
โ”‚  The split is the Prettier recommendation.                  โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

Summary

ItemValue
Shared configA workspace package
Package reference"prettier": "@myorg/prettier-config"
Package overrideImport and spread
Prettier dependencypeerDependencies
Plugin dependencydependencies
pnpm workaroundpublicHoistPattern: ['*prettier*']

Key takeaways:

  • The shared config is a workspace package. A package that exports a Prettier config object is installable, versionable, and able to declare its own plugins. A root-level file is simpler but not installable, and it does not have the plugin declaration mechanism .
  • The package reference is the "prettier" key or a local config file. A package that wants the shared settings without overrides uses the "prettier" key. A package that needs overrides uses a local config file that imports the shared config and spreads it into a new object .
  • The spread is shallow, so the plugins array is replaced. A package that adds a plugin must spread the shared plugins array and append the new plugin. A package that replaces the array loses the shared plugins.
  • The shared config declares Prettier as a peer dependency and the plugins as dependencies. The peer dependency reflects the fact that the consumer already has Prettier. The plugin dependencies reflect the fact that the config is the only thing that needs them .
  • The pnpm plugin resolution issue has a workaround. Prettier resolves a plugin relative to the working directory, not the config file. A pnpm monorepo that runs Prettier from the root must add the plugin to publicHoistPattern so the resolution can find it .

Remember: A monorepo’s Prettier configuration is a shared package and a set of per-package references. The shared package exports the config and declares the plugins. The package reference uses the "prettier" key or a local config that imports and spreads the shared config. The per-package config stops the upward search, so the shared config is only used when the package explicitly references it. The plugin declaration is the split between the peer dependency and the plugin dependencies. The pnpm workaround is the fix for the plugin resolution issue. The shared config is the baseline, and the per-package config is the exception.

Was this helpful?


Stop using slow, ad-bloated tool sites! ๐Ÿคฎ

๐Ÿ”Ž Search “KandZ Tools” on Google to use many professional utilities for free.

KandZ.me is the ultimate minimalist hub for:
โœ… Finance (Mortgage, Interest, Inflation)
โœ… Tech (Base64, JSON, Dev Suite, IP)
โœ… Health (BMI, BMR, TDEE)
โœ… Productivity (Timer, Workspace, QR)

โšก๏ธ Fast & Private
๐Ÿ”’ No data leaves your device
๐Ÿ’Ž 100% Free

๐Ÿ”— Use it now: https://tools.kandz.me
๐Ÿ”– Bookmark itโ€”youโ€™ll need it later!