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 a `Forbidden` error when a document create/update tries to consume a staged upload that belongs to a different collection or a different user than the one making the request. The staged upload JWT embeds `collectionSlug` and `user`; both must match the current request.

Source

Thrown at packages/payload/src/uploads/stagedUpload.ts:168

  collectionSlug: string
  req: PayloadRequest
  uploadReference: unknown
}): Promise<File> => {
  if (
    !uploadReference ||
    typeof uploadReference !== 'object' ||
    Array.isArray(uploadReference) ||
    !('uploadId' in uploadReference) ||
    typeof uploadReference.uploadId !== 'string'
  ) {
    throw new APIError('Invalid staged upload.', 400)
  }

  const { uploadId } = uploadReference
  const upload = await verifyUploadID(req, uploadId)

  if (upload.collectionSlug !== collectionSlug || upload.user !== getUser(req)) {
    throw new Forbidden(req.t)
  }

  const directory = await getUploadDirectory(req, upload.collectionSlug)
  const tempFilePath = path.join(directory, upload.id)
  let data: Buffer

  try {
    data = await fs.readFile(tempFilePath)
  } catch {
    throw new APIError('Staged upload was not found.', 400)
  }

  if (data.length !== upload.filesize) {
    await fs.rm(tempFilePath, { force: true })
    throw new APIError('Staged upload is incomplete.', 400)
  }

  await fs.rm(tempFilePath, { force: true })

View on GitHub (pinned to 00c58b35c0)

Solutions

  1. Ensure the same authenticated session that called `generateStagedUploadInstructions` also submits the create/update request.
  2. Confirm the collection slug in the upload instructions matches the collection slug of the target document operation.
  3. Do not reuse uploadIds across users -- each user must stage their own upload.
  4. If impersonation or admin-on-behalf-of flows are needed, generate the instructions in the target user's context.

Example fix

// before -- instructions generated as user A, consumed as user B
const instr = await adminClient.generateInstructions({ collectionSlug: 'media', ... })
await userBClient.create({ collection: 'media', data: { file: { uploadReference: { uploadId: instr.file.uploadReference.uploadId } } } })

// after -- same session for both steps
const instr = await userClient.generateInstructions({ collectionSlug: 'media', ... })
await userClient.create({ collection: 'media', data: { file: { uploadReference: { uploadId: instr.file.uploadReference.uploadId } } } })
Defensive patterns

Strategy: validation

Validate before calling

// Ensure the same user session and collection are used for staging and consuming
const userKey = `${req.user.collection}:${req.user.id}`
// The uploadId JWT embeds user and collectionSlug -- regenerate if either changes
if (sessionChanged) {
  const fresh = await generateStagedUploadInstructions({ collectionSlug, req, ... })
}

Try / catch

try {
  await payload.create({ collection, data })
} catch (e) {
  if (e.name === 'Forbidden') {
    // re-authenticate or re-stage in the correct user context
  } else throw e
}

Prevention

When it happens

Trigger: The `uploadId` was generated for collection A but the create/update targets collection B; or the `uploadId` was generated by user X but user Y (or an anonymous request) submits the document that references it.

Common situations: Sharing an uploadId across users in a multi-tenant app; generating instructions for one collection but POSTing to another; a session changed (logout / re-login as different user) between staging and consuming the upload; the `getUser` key (`collection:id`) differs because the auth strategy changed.

Related errors


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