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 during updateByID when no document is found BUT the collection's update access resolved to a where-policy. Payload cannot tell whether the document does not exist or merely is access-restricted, so it returns HTTP 403 Forbidden to avoid leaking existence. This is the standard access-control information-leak prevention pattern.

Source

Thrown at packages/payload/src/collections/operations/updateByID.ts:174

      req,
      where: fullWhere,
    }

    const docWithLocales = await getLatestCollectionVersion<
      RequiredDataFromCollectionSlug<TSlug> & TypeWithID
    >({
      id,
      config: collectionConfig,
      payload,
      query: findOneArgs,
      req,
    })

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

    // /////////////////////////////////////
    // Generate data for all files and sizes
    // /////////////////////////////////////

    const { data: newFileData, files: filesToUpload } = await generateFileData({
      collection,
      config,
      data,
      operation: 'update',
      overwriteExistingFiles,
      req,
      throwOnMissingFile: false,
    })

View on GitHub (pinned to 00c58b35c0)

Solutions

  1. Ensure the calling user actually has update access to that document (check the access predicate locally first).
  2. Return a clear 'not allowed' message to the end user; do not retry with different ids.
  3. If the behavior is wrong, audit the collection's `access.update` function — it may be over-restrictive.

Example fix

// before
await payload.update({ collection: 'posts', id, data, req, overrideAccess: false })
// after
// Verify ownership first using a read-scoped query
const doc = await payload.findByID({ collection: 'posts', id, req })
if (!doc) throw new ForbiddenError('cannot update this post')
await payload.update({ collection: 'posts', id, data, req })
Defensive patterns

Strategy: try-catch

Validate before calling

// Enforce access locally before update by attempting a scoped read
const doc = await payload.findByID({ collection, id, req, depth: 0 }).catch(() => null)
if (!doc) {
  throw new ForbiddenError('User cannot update this document')
}
await payload.update({ collection, id, data, req })

Try / catch

try {
  await payload.update({ collection, id, data, req })
} catch (err) {
  if (err instanceof Forbidden) {
    // access-restricted (or does not exist) — do not leak which
    return respond(403, 'Not allowed')
  }
  throw err
}

Prevention

When it happens

Trigger: A user whose `access.update` returns a `{ where: {...} }` constraint tries to update a document they cannot see; updating an id that belongs to another tenant; the document exists but falls outside the where-clause scope.

Common situations: Multi-tenant setups where `update` access is scoped by `tenant`/`user`; a logged-in user editing another user's content; role-based access returning a restrictive where clause.

Related errors


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