{"record":{"id":"438d6f3d80f3b689","repo":"payloadcms/payload","slug":"failed-to-fetch-file-from-fileurl","errorCode":null,"errorMessage":"Failed to fetch file from ${fileURL}","messagePattern":"Failed to fetch file from (.+?)","errorType":"http","errorClass":"APIError","httpStatus":null,"severity":"error","filePath":"packages/payload/src/uploads/getExternalFile.ts","lineNumber":95,"sourceCode":"        const location = res.headers.get('location')\n        if (location) {\n          fileURL = new URL(location, fileURL).toString()\n          if (\n            uploadConfig.pasteURL &&\n            uploadConfig.pasteURL.allowList &&\n            !isURLAllowed(fileURL, uploadConfig.pasteURL.allowList)\n          ) {\n            throw new APIError('Redirect target is not allowed.', 400)\n          }\n          continue\n        }\n      }\n\n      break\n    }\n\n    if (!res || !res.ok) {\n      throw new APIError(`Failed to fetch file from ${fileURL}`, res?.status)\n    }\n\n    const data = await res.arrayBuffer()\n\n    return {\n      name: filename,\n      data: Buffer.from(data),\n      mimetype: res.headers.get('content-type') || undefined!,\n      size: Number(res.headers.get('content-length')) || 0,\n    }\n  }\n\n  throw new APIError('Invalid file url', 400)\n}\n","sourceCodeStart":77,"sourceCodeEnd":110,"githubUrl":"https://github.com/payloadcms/payload/blob/00c58b35c0ed348ddc22daabf467b139727214fd/packages/payload/src/uploads/getExternalFile.ts#L77-L110","documentation":"After the redirect loop resolves, `getExternalFile` checks `res.ok`. If the final response is missing (`res` null) or its status is not 2xx, Payload throws `APIError` with that status (or 500 if `res` is null). The thrown message is `Failed to fetch file from <fileURL>` and the HTTP status mirrors the upstream failure — so a 404 upstream surfaces as HTTP 404 here.","triggerScenarios":"`getExternalFile` completes the fetch/redirect loop with a non-2xx final status (404 Not Found, 403 Forbidden, 401, 500, 502, 503…), or `res` is somehow null/undefined after the loop. Triggered during duplication/re-upload of a remote file, or via a paste-URL upload.","commonSituations":"The stored/remote URL 404s (file deleted, bucket key changed, expired signed URL). The remote host returns 403 (permissions, hot-link protection, geo-block). The origin is down (5xx). Authentication headers were stripped (`trimAuthCookies`) and the upstream now rejects. Network/proxy returns a non-2xx gateway error.","solutions":["Open the same URL in a browser/curl from the server to see the real upstream status code and body.","For 404/410: the remote file is gone — re-upload or update the document's URL.","For 403/401: ensure the fetch carries required auth (review `externalFileHeaderFilter` / cookie trimming) or use a publicly reachable URL.","For 5xx: retry once the origin recovers, or mirror the file to your own storage.","Confirm `uploadConfig.externalFileHeaderFilter` isn't dropping a header the upstream requires."],"exampleFix":"// before — fetching a URL that 404s at runtime\nawait payload.update({ collection: 'media', id, file: { data: Buffer.alloc(0), name, mimetype, size } })\n// where doc.url points to a deleted file\n\n// after — verify reachability before re-upload\nconst ok = await fetch(doc.url).then(r => r.ok)\nif (!ok) {\n  await payload.update({ collection: 'media', id, data: { url: newUrl } })\n}","handlingStrategy":"try-catch","validationCode":"async function isFetchable(url: string): Promise<boolean> {\n  try {\n    const res = await fetch(url, { method: 'HEAD' })\n    return res.ok\n  } catch {\n    return false\n  }\n}\n\nif (!(await isFetchable(doc.url))) {\n  // URL is not reachable — update it or upload bytes directly\n}","typeGuard":"function isHttpOkStatus(status: number | undefined): boolean {\n  return typeof status === 'number' && status >= 200 && status < 300\n}","tryCatchPattern":"try {\n  await payload.update({ collection: 'media', id, data: { url } })\n} catch (err) {\n  if (err instanceof Error && /failed to fetch file from/i.test(err.message)) {\n    const status = (err as any).status\n    if (status === 404) { /* file gone — re-upload */ }\n    else if (status >= 500) { /* transient — retry with backoff */ }\n    else { /* 4xx auth — fix headers */ }\n  } else throw err\n}","preventionTips":["Verify remote URLs return 2xx before persisting or re-uploading.","Keep signed URLs fresh; refresh before they expire.","Ensure required auth headers survive `externalFileHeaderFilter`."],"tags":["upload","network","external-file","http-status"],"backgroundTag":null,"analyzedSha":"00c58b35c0ed348ddc22daabf467b139727214fd","analyzedAt":"2026-08-12T20:45:03.758Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}