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 Forbidden (HTTP 403) by the S3 storage adapter's generateUploadInstructions generator. It fires when overrideAccess is falsy and either the configured access callback denies the request, or no access callback is set and req.user is absent. This gates the generation of presigned client-upload URLs.

Source

Thrown at packages/storage-s3/src/generateUploadInstructions.ts:36

export const generateUploadInstructions = ({
  access,
  acl,
  bucket,
  collectionPrefix,
  getStorageClient,
  useCompositePrefixes = false,
}: Args): GenerateUploadInstructions => {
  return async ({
    collectionSlug,
    docPrefix,
    filename,
    filesize,
    mimeType,
    overrideAccess,
    req,
  }) => {
    if (!overrideAccess && (access ? !(await access({ collectionSlug, req })) : !req.user)) {
      throw new Forbidden(req.t)
    }

    let filesizeLimit = req.payload.config.upload.limits?.fileSize

    if (filesizeLimit === Infinity) {
      filesizeLimit = undefined
    }

    const { fileKey, sanitizedDocPrefix, sanitizedFilename } = await resolveSignedURLKey({
      collectionPrefix,
      collectionSlug,
      docPrefix,
      filename,
      req,
      useCompositePrefixes,
    })

    const signableHeaders = new Set<string>()

View on GitHub (pinned to 00c58b35c0)

Solutions

  1. Forward the user's authentication token/cookie when the client requests upload instructions.
  2. For trusted server-side generation, pass overrideAccess: true in the generateUploadInstructions args.
  3. Provide an `access` callback to the S3 adapter matching the intended client-upload policy.
  4. Verify the logged-in user's role satisfies the collection's create access.

Example fix

// before
const instructions = await collection.upload({
  data: { filename },
  file,
  // user not attached → Forbidden
})

// after — trusted server context
const instructions = await generateUploadInstructions({
  collectionSlug: 'media',
  filename,
  overrideAccess: true,
  req,
})
Defensive patterns

Strategy: validation

Validate before calling

function canRequestUploadInstructions(args: {
  overrideAccess?: boolean
  user?: unknown
  accessResult?: boolean
}): boolean {
  return Boolean(args.overrideAccess || args.user || args.accessResult)
}

if (!canRequestUploadInstructions({ overrideAccess, user: req.user })) {
  throw new Error('Authentication required to request upload instructions')
}

Type guard

import { Forbidden } from 'payload'

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

Try / catch

try {
  instructions = await generateUploadInstructions({ collectionSlug, filename, req })
} catch (err) {
  if (err instanceof Forbidden) {
    // prompt re-auth or surface 'no permission'
  }
  throw err
}

Prevention

When it happens

Trigger: A client requests upload instructions (presigned URL) without overrideAccess, and either a custom access function returns false or there is no access function and the user is not logged in.

Common situations: Frontend directly calling generateUploadInstructions without forwarding the auth token; a server-to-server path forgot to set overrideAccess: true; access policy returns false for the user's role; expired session.

Related errors


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