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 (HTTP 403) from the R2 multipart-upload handler after the access callback returns false. The access callback defaults to defaultR2ClientUploadsAccess, which delegates to the collection's config.access.create policy, falling back to a logged-in user check.

Source

Thrown at packages/storage-r2/src/handleMultiPartUpload.ts:50

export const getHandleMultiPartUpload =
  ({
    access = defaultR2ClientUploadsAccess,
    bucket,
    collections,
    useCompositePrefixes = false,
  }: Args): PayloadHandler =>
  async (req) => {
    const params = Object.fromEntries(req.searchParams) as R2StorageMultipartUploadHandlerParams
    const collectionSlug = params.collection
    const filetype = params.fileType

    const collectionConfig = collections[collectionSlug]
    if (!collectionConfig) {
      throw new APIError(`Collection ${collectionSlug} was not found in R2 Storage options`)
    }

    if (!(await access({ collectionSlug, req }))) {
      throw new Forbidden(req.t)
    }

    const collectionPrefix = (typeof collectionConfig === 'object' && collectionConfig.prefix) || ''
    const { fileKey, sanitizedFilename } = await resolveSignedURLKey({
      collectionPrefix,
      collectionSlug,
      docPrefix: params.docPrefix ?? undefined,
      filename: params.fileName,
      req,
      useCompositePrefixes,
    })

    const multipartId = params.multipartId
    const multipartKey = params.multipartKey
    const multipartNumber = parseInt(params.multipartNumber || '')

    if (multipartId && multipartKey) {
      const multipartUpload = bucket.resumeMultipartUpload(multipartKey, multipartId)

View on GitHub (pinned to 00c58b35c0)

Solutions

  1. Ensure the requester is authenticated (session cookie / token present) before initiating the multipart upload.
  2. Review the collection's config.access.create policy and confirm it permits the user's role.
  3. If public uploads are intended, set an access function on the R2 adapter that returns true for the relevant collection.
  4. Pass a custom `access` callback to the R2 adapter that reflects the intended policy instead of the default.

Example fix

// before — default access requires a logged-in user
const r2Adapter = new R2Storage({ bucket, collections: { media: {} } })

// after — allow public uploads to 'media' only
const r2Adapter = new R2Storage({
  bucket,
  collections: { media: {} },
  access: async ({ collectionSlug, req }) =>
    collectionSlug === 'media' ? true : Boolean(req.user),
})
Defensive patterns

Strategy: try-catch

Validate before calling

// Before initiating, ensure the user is authenticated
if (!user) {
  throw new Error('Sign in required to upload files.')
}
// Optionally call the collection's create access locally if accessible

Type guard

import { Forbidden } from 'payload'

function isForbidden(err: unknown): err is Forbidden {
  return err instanceof Forbidden
}

Try / catch

try {
  await initiateMultipartUpload({ collection: slug })
} catch (err) {
  if (err instanceof Forbidden) {
    // redirect to login or show 'permission denied'
    redirectToLogin()
    return
  }
  throw err
}

Prevention

When it happens

Trigger: An authenticated (or anonymous) user initiates a multipart upload and the access function resolves to false: either config.access.create returned false, or no access function and req.user is null, or a custom access function denied the request.

Common situations: Public/unauthenticated upload attempt on a collection whose create access requires a user; a logged-in user whose role lacks create permission; a custom access function with a bug that returns falsy for valid users; access policy depends on req.user data that is missing.

Related errors


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