payloadcms/payload · error · Error

Cannot provide both collectionSlug and globalSlug

Error message

Cannot provide both collectionSlug and globalSlug

What it means

localizeStatus treats collectionSlug and globalSlug as mutually exclusive because it must pick the right versions collection and config lookup. Thrown when both are supplied at once.

Source

Thrown at packages/db-mongodb/src/predefinedMigrations/migrateLocalizeStatus.ts:85

    await cleanupSnapshotDocuments({ connection, entitySlug: global.slug, payload, session })
  }

  payload.logger.info({ msg: 'localize-status migration completed successfully' })
}

/**
 * Converts version._status (and the main document _status) from a scalar string to a
 * per-locale object for a single collection or global.
 */
export async function localizeStatus(args: LocalizeStatusArgs): Promise<void> {
  const { collectionSlug, globalSlug, payload, req, session } = args

  if (!collectionSlug && !globalSlug) {
    throw new Error('Either collectionSlug or globalSlug must be provided')
  }

  if (collectionSlug && globalSlug) {
    throw new Error('Cannot provide both collectionSlug and globalSlug')
  }

  const entitySlug = collectionSlug || globalSlug
  // MongoDB collection names are case-insensitive and stored as lowercase
  const versionsCollection = `_${entitySlug}_versions`.toLowerCase()

  if (!payload.config.localization) {
    throw new Error('Localization is not enabled in payload config')
  }

  // Check if versions are enabled on this collection/global
  let entityConfig
  if (collectionSlug) {
    const collection = payload.config.collections.find((c) => c.slug === collectionSlug)
    if (collection) {
      entityConfig = collection
    }
  } else if (globalSlug) {

View on GitHub (pinned to 00c58b35c0)

Solutions

  1. Pass exactly one identifier: collectionSlug OR globalSlug, never both.
  2. Loop collections and globals in separate branches, each calling localizeStatus with only the relevant slug.
  3. Add an assertion/log in your caller to confirm only one is set before invoking.
  4. Use the migrateLocalizeStatus() orchestrator which handles the split correctly.

Example fix

// before
await localizeStatus({
  collectionSlug: 'posts',
  globalSlug: 'header',
  payload,
  req,
})

// after — two separate calls
await localizeStatus({ collectionSlug: 'posts', payload, req })
await localizeStatus({ globalSlug: 'header', payload, req })
Defensive patterns

Strategy: validation

Validate before calling

function assertMutuallyExclusive(args: { collectionSlug?: string; globalSlug?: string }) {
  if (args.collectionSlug && args.globalSlug) {
    throw new Error('Pass collectionSlug OR globalSlug, not both')
  }
}

Type guard

function isExactlyOneEntity(a: { collectionSlug?: string; globalSlug?: string }): boolean {
  return Boolean(a.collectionSlug) !== Boolean(a.globalSlug)
}

Prevention

When it happens

Trigger: Calling localizeStatus() with both collectionSlug and globalSlug set, e.g. from a custom loop that iterates both lists and accidentally merges the arguments.

Common situations: A custom migration script building args dynamically that ends up populating both fields; copy-paste error when adding a new entity.

Related errors


AI-assisted analysis of payloadcms/payload@00c58b35c0 (2026-08-12). Data as JSON: /api/errors/81a6645be3f6506a. Report an issue: GitHub.