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 as `new Forbidden(req.t)` (HTTP 403) at packages/payload/src/collections/operations/restoreVersion.ts:129 during restoreVersion when the parent document is not found AND a where-based access policy is in effect. The distinction from error 155 matters: when access control returns a `where` clause and the doc is absent, Payload reports Forbidden (not NotFound) to avoid leaking the existence of documents the user cannot see.

Source

Thrown at packages/payload/src/collections/operations/restoreVersion.ts:129

    // /////////////////////////////////////
    // Retrieve document
    // /////////////////////////////////////

    const findOneArgs: FindOneArgs = {
      collection: collectionConfig.slug,
      locale: 'all',
      req,
      where: combineQueries({ id: { equals: parentDocID } }, accessResults),
    }

    // Get the document from the non versioned collection
    const doc = await req.payload.db.findOne<TData>(findOneArgs)

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

    if (collectionConfig.trash && doc?.deletedAt) {
      throw new APIError(
        `Cannot restore a version of a trashed document (ID: ${parentDocID}). Restore the document first.`,
        httpStatus.FORBIDDEN,
      )
    }

    // /////////////////////////////////////
    // fetch previousDoc
    // /////////////////////////////////////
    const prevDocWithLocales = await getLatestCollectionVersion({
      id: parentDocID,
      config: collectionConfig,
      payload,
      query: findOneArgs,
      req,

View on GitHub (pinned to 00c58b35c0)

Solutions

  1. Confirm the authenticated user has `update` access to the parent document's collection.
  2. Inspect the collection's `access.update` function and the where clause it returns for this user.
  3. If overriding access server-side is acceptable, pass `overrideAccess: true` (use sparingly and never with untrusted callers).

Example fix

// before — user lacks update access, restoreVersion throws Forbidden
await payload.restoreVersion({ collection: 'pages', id: versionId, req })

// after — escalate to a service account that is allowed to update, or fix the access policy
await payload.restoreVersion({ collection: 'pages', id: versionId, overrideAccess: true, req })
// (only in trusted server contexts; never expose overrideAccess:true to client input)
Defensive patterns

Strategy: try-catch

Validate before calling

// Access policies are server-side; the reliable pre-check is a findByID with the user's req
const visible = await payload.findByID({
  collection: 'pages',
  id: parentDocID,
  req, // applies the user's access.update where policy
  disableErrors: true,
})
if (!visible) {
  // user cannot see/update this document — do not attempt restoreVersion
}

Type guard

// No static type guard applies; access is runtime-evaluated.
// Expose a helper that runs the access check:
const canRestore = async (payload: Payload, collection: CollectionSlug, parentId: string | number, req: PayloadRequest) => {
  const doc = await payload.findByID({ collection, id: parentId, req, disableErrors: true })
  return Boolean(doc)
}

Try / catch

try {
  await payload.restoreVersion({ collection: 'pages', id: versionId, req })
} catch (err) {
  if (err instanceof Forbidden) {
    // user lacks update access (or doc hidden by a where policy) — return 403
  } else throw err
}

Prevention

When it happens

Trigger: A user without update access attempts to restore a version of a document they cannot read; the collection's `access.update` returns a where filter that excludes the parent doc; the parent doc was deleted but the access policy is a where clause (so 403 is chosen over 404 to prevent information disclosure).

Common situations: Role-based access where the user's policy hides certain docs; a multi-tenant where clause (`tenant: { equals: currentUser.tenant }`) that does not match the version's parent; restoring across locales/tenants.

Related errors


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