pnpm/pnpm · error · InvalidWorkspaceManifestError

Invalid catalog entry for ${alias}. Expected string, but fou

Error message

Invalid catalog entry for ${alias}. Expected string, but found: ${typeof specifier}

What it means

Every entry in the default `catalog` of pnpm-workspace.yaml must map a package alias to a string version specifier. `assertValidWorkspaceManifestCatalog` iterates `Object.entries(manifest.catalog)` and throws this when an entry's value is not a string — e.g. a number, boolean, or a nested map.

Source

Thrown at pnpm11/workspace/workspace-manifest-reader/src/catalogs.ts:26

  [dependencyName: string]: string
}

export function assertValidWorkspaceManifestCatalog (manifest: { packages?: readonly string[], catalog?: unknown }): asserts manifest is { catalog?: WorkspaceCatalog } {
  if (manifest.catalog == null) {
    return
  }

  if (Array.isArray(manifest.catalog)) {
    throw new InvalidWorkspaceManifestError('Expected catalog field to be an object, but found - array')
  }

  if (typeof manifest.catalog !== 'object') {
    throw new InvalidWorkspaceManifestError(`Expected catalog field to be an object, but found - ${typeof manifest.catalog}`)
  }

  for (const [alias, specifier] of Object.entries(manifest.catalog)) {
    if (typeof specifier !== 'string') {
      throw new InvalidWorkspaceManifestError(`Invalid catalog entry for ${alias}. Expected string, but found: ${typeof specifier}`)
    }
  }
}

export function assertValidWorkspaceManifestCatalogs (manifest: { packages?: readonly string[], catalogs?: unknown }): asserts manifest is { catalogs?: WorkspaceNamedCatalogs } {
  if (manifest.catalogs == null) {
    return
  }

  if (Array.isArray(manifest.catalogs)) {
    throw new InvalidWorkspaceManifestError('Expected catalogs field to be an object, but found - array')
  }

  if (typeof manifest.catalogs !== 'object') {
    throw new InvalidWorkspaceManifestError(`Expected catalogs field to be an object, but found - ${typeof manifest.catalogs}`)
  }

  for (const [catalogName, catalog] of Object.entries(manifest.catalogs)) {

View on GitHub (pinned to 6261b7f388)

Solutions

  1. Quote every catalog specifier in pnpm-workspace.yaml so YAML keeps it a string: `catalog:\n foo: "1.2"`
  2. Flatten nested objects to a plain `alias: specifier` string pair
  3. Verify with `pnpm install` after editing to confirm the manifest now parses
  4. Add an editor YAML schema for pnpm-workspace.yaml to catch this as you type

Example fix

# before
catalog:
  foo:
    version: 1.2

# after
catalog:
  foo: "1.2"
Defensive patterns

Strategy: try-catch

Validate before calling

const specifiers = Object.values(manifest.catalog ?? {})
if (specifiers.some((v) => typeof v !== 'string')) {
  throw new Error('catalog entries must all be string specifiers — quote numeric-looking versions')
}

Type guard

function isValidCatalog (catalog: unknown): boolean {
  return catalog == null ||
    (typeof catalog === 'object' && !Array.isArray(catalog) &&
      Object.entries(catalog).every(([, v]) => typeof v === 'string'))
}

Try / catch

try {
  validateWorkspaceManifest(manifest)
} catch (err: unknown) {
  if (util.types.isNativeError(err) && 'code' in err && err.code === 'ERR_PNPM_INVALID_WORKSPACE_CONFIGURATION' && err.message.includes('Invalid catalog entry')) {
    // surface the alias from the message and fix pnpm-workspace.yaml
  }
  throw err
}

Prevention

When it happens

Trigger: A `catalog` mapping where a value YAML-parses as a non-string: `catalog:\n foo: 123` (number), `foo: true` (boolean), or `foo:\n version: 1.0.0` (nested object). Triggered whenever `validateWorkspaceManifest` runs on that manifest, i.e. any pnpm command in the workspace.

Common situations: Bare semver-like values that YAML coerces to numbers (`1.2` or `1e10` style specifiers), reusing an npm-shrinkwrap/JSON structure with nested `{version, specifier}` objects, or `yes`/`no`/`on`/`off` style values that YAML parses as booleans.

Related errors


AI-assisted analysis of pnpm/pnpm@6261b7f388 (2026-08-17). Data as JSON: /api/errors/f87b1b189cff0c75. Report an issue: GitHub.