payloadcms/payload · error · Forbidden

You are not allowed to perform this action.

Error message

You are not allowed to perform this action.

What it means

Thrown by `getDuplicateDocumentData` when the source document is not found BUT a where-based read access policy is active. As with updateByID, Payload returns 403 rather than 404 to avoid revealing whether the document exists. The caller either lacks read access or the document is outside their where-clause scope.

Source

Thrown at packages/payload/src/duplicateDocument/index.ts:76

    payload,
    query: findOneArgs,
    req,
  })

  if (selectedLocales && selectedLocales.length > 0 && duplicatedFromDocWithLocales) {
    duplicatedFromDocWithLocales = filterDataToSelectedLocales({
      configBlockReferences: payload.config.blocks,
      docWithLocales: duplicatedFromDocWithLocales,
      fields: collectionConfig.fields,
      selectedLocales,
    })
  }

  if (!duplicatedFromDocWithLocales && !hasWherePolicy) {
    throw new NotFound(req.t)
  }
  if (!duplicatedFromDocWithLocales && hasWherePolicy) {
    throw new Forbidden(req.t)
  }

  // remove the createdAt timestamp and rely on the db to set it
  if ('createdAt' in duplicatedFromDocWithLocales) {
    delete duplicatedFromDocWithLocales.createdAt
  }
  // remove the id and rely on the db to set it
  if ('id' in duplicatedFromDocWithLocales) {
    delete duplicatedFromDocWithLocales.id
  }

  duplicatedFromDocWithLocales = await beforeDuplicate({
    id,
    collection: collectionConfig,
    context: req.context,
    doc: duplicatedFromDocWithLocales,
    overrideAccess: overrideAccess!,
    req,

View on GitHub (pinned to 00c58b35c0)

Solutions

  1. Confirm the caller has read access to the source document (run a scoped `findByID` first).
  2. If the operation is privileged/internal, use `overrideAccess: true` deliberately.
  3. Return a clear 'not allowed' message; do not leak existence by retrying different ids.

Example fix

// before
await payload.duplicate({ collection: 'posts', id, req, overrideAccess: false })
// after
const src = await payload.findByID({ collection: 'posts', id, req }) // enforces read access
if (!src) throw new ForbiddenError('cannot duplicate this post')
await payload.duplicate({ collection: 'posts', id, req })
Defensive patterns

Strategy: try-catch

Validate before calling

const src = await payload.findByID({ collection, id, req, depth: 0 }).catch(() => null)
if (!src) throw new ForbiddenError('Cannot duplicate this document')
await payload.duplicate({ collection, id, req })

Try / catch

try {
  await payload.duplicate({ collection, id, req })
} catch (err) {
  if (err instanceof Forbidden) return respond(403, 'Not allowed')
  throw err
}

Prevention

When it happens

Trigger: A user whose `access.read` returns `{ where: {...} }` tries to duplicate a document outside that scope; cross-tenant duplication; duplicating a private doc as a non-owner.

Common situations: Multi-tenant read scoping; role-based access where users can only see their own docs; an admin tool duplicating across tenants without `overrideAccess`.

Related errors


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