{"record":{"id":"61fac2adb6576111","repo":"payloadcms/payload","slug":"you-are-not-allowed-to-perform-this-action-61fac2","errorCode":null,"errorMessage":"You are not allowed to perform this action.","messagePattern":"You are not allowed to perform this action\\.","errorType":"http","errorClass":"Forbidden","httpStatus":403,"severity":"error","filePath":"packages/payload/src/uploads/endpoints/uploadInstructions.ts","lineNumber":72,"sourceCode":"  }\n\n  await checkFileRestrictions({\n    checkFileContents: false,\n    collection: collection.config,\n    file: {\n      name: upload.filename,\n      data: Buffer.alloc(0),\n      mimetype: upload.mimeType,\n      size: upload.filesize,\n    },\n    req,\n  })\n\n  if (!uploadInstructions && !overrideAccess) {\n    // Staged uploads write to Payload before a document is saved. Require a signed-in user who\n    // can create or update documents in this collection.\n    if (!req.user) {\n      throw new Forbidden(req.t)\n    }\n\n    const collectionPermissions = (await getAccessResults({ req })).collections?.[\n      upload.collectionSlug\n    ]\n\n    if (!collectionPermissions?.create && !collectionPermissions?.update) {\n      throw new Forbidden(req.t)\n    }\n  }\n\n  return uploadInstructions\n    ? uploadInstructions.generate({ ...upload, overrideAccess, req })\n    : generateStagedUploadInstructions({ ...upload, req })\n}\n\nexport const uploadInstructionsEndpoint: Endpoint = {\n  handler: async (req) => {","sourceCodeStart":54,"sourceCodeEnd":90,"githubUrl":"https://github.com/payloadcms/payload/blob/00c58b35c0ed348ddc22daabf467b139727214fd/packages/payload/src/uploads/endpoints/uploadInstructions.ts#L54-L90","documentation":"The staged-upload path (used when no storage adapter provides `uploadInstructions`) writes the file to Payload-local disk *before* a document is created or updated. Because that write happens outside a document transaction, Payload requires an authenticated `req.user`. If `req.user` is absent on the `POST /upload-instructions` request, a `Forbidden` (HTTP 403) is thrown. This applies only when the collection has no adapter `uploadInstructions` and `overrideAccess` is not set.","triggerScenarios":"`POST /api/upload-instructions` reaches `getUploadInstructions` with `overrideAccess` falsy, the target collection has no `config.upload.uploadInstructions` (i.e. local staged uploads), and the request carries no authenticated session (`req.user` is undefined).","commonSituations":"A custom upload client (CLI, mobile app, external service) calls the endpoint without sending the Payload session cookie or a Bearer/strategy token. A reverse proxy strips auth headers/cookies. The route is hit during SSR or a cron job with no logged-in user. A storage adapter (S3, R2, Vercel Blob) was removed or not registered, silently flipping the collection from adapter uploads to staged uploads and now requiring auth it did not before.","solutions":["Authenticate the request: send the Payload session cookie (`payload=<prefix>`), or call the endpoint from a context that runs `payload.login`/`payload.create` with a strategy token first.","If this is a trusted server-side flow, call `getUploadInstructions` (or `payload.create`/`update`) with `overrideAccess: true` from the Local API.","Re-add the storage adapter if uploads were meant to go straight to S3/R2/etc., which removes the staged-upload auth requirement.","Check middleware/proxies that may be dropping `Cookie`/`Authorization` headers."],"exampleFix":"// before — no auth on the client\nawait fetch(`${serverURL}/api/upload-instructions`, {\n  method: 'POST',\n  headers: { 'Content-Type': 'application/json' },\n  body: JSON.stringify({ collectionSlug, filename, filesize, mimeType }),\n})\n\n// after — include session cookie / token\nawait fetch(`${serverURL}/api/upload-instructions`, {\n  method: 'POST',\n  headers: { 'Content-Type': 'application/json', Cookie: `payload=${token}` },\n  body: JSON.stringify({ collectionSlug, filename, filesize, mimeType }),\n})","handlingStrategy":"validation","validationCode":"// Ensure req.user is populated before calling the staged path\nimport { getPayload } from 'payload'\nimport config from './payload.config'\n\nconst payload = await getPayload({ config })\n\nasync function getInstructionsAsUser(user: { email: string; password: string }) {\n  const { token } = await payload.login({ collection: 'users', data: user })\n  const req = await payload.request({ collection: 'media' }) // or build a req with req.user\n  // attach the user to the request before calling getUploadInstructions\n  return payload.getUploadInstructions\n    ? null\n    : null\n}\n// Simpler: hit the REST endpoint with the cookie\nasync function postInstructions(token: string, body: object) {\n  return fetch(`${serverURL}/api/upload-instructions`, {\n    method: 'POST',\n    headers: { 'Content-Type': 'application/json', Cookie: `payload=${token}` },\n    body: JSON.stringify(body),\n  })\n}","typeGuard":"function hasUser(req: { user?: unknown }): req is { user: Record<string, unknown> } {\n  return !!req.user && typeof req.user === 'object'\n}\n\nif (!hasUser(req)) {\n  // authenticate or use overrideAccess: true on the Local API\n}","tryCatchPattern":"const res = await fetch(`${url}/api/upload-instructions`, { method: 'POST', headers, body })\nif (res.status === 403) {\n  const { message } = await res.json().catch(() => ({}))\n  if (/not allowed/i.test(message ?? '')) {\n    // no session — prompt login / refresh token, then retry\n  }\n}","preventionTips":["Always send the Payload session cookie or a strategy token on upload-instructions requests.","For server-side trusted flows, call the Local API with `overrideAccess: true`.","Keep the storage adapter registered so the staged path (and its auth requirement) isn't accidentally engaged."],"tags":["upload","authentication","staged-upload","authorization"],"backgroundTag":null,"analyzedSha":"00c58b35c0ed348ddc22daabf467b139727214fd","analyzedAt":"2026-08-12T20:45:03.758Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}