{"record":{"id":"f3226eebcb1eded5","repo":"payloadcms/payload","slug":"too-many-redirects","errorCode":null,"errorMessage":"Too many redirects.","messagePattern":"Too many redirects\\.","errorType":"http","errorClass":"APIError","httpStatus":403,"severity":"error","filePath":"packages/payload/src/uploads/endpoints/getFileFromURL.ts","lineNumber":93,"sourceCode":"      // Allow-listed URLs bypass SSRF filtering (e.g. internal/localhost CDNs)\n      response = await fetch(fileURL, {\n        headers: { 'Accept-Encoding': 'identity' },\n        redirect: 'manual',\n        signal: AbortSignal.timeout(30_000),\n      })\n    } else {\n      response = await safeFetch(fileURL, {\n        headers: {\n          'Accept-Encoding': 'identity',\n        },\n        signal: AbortSignal.timeout(30_000),\n      })\n    }\n\n    if (response.status >= 300 && response.status < 400) {\n      redirectCount++\n      if (redirectCount > maxRedirects) {\n        throw new APIError('Too many redirects.', 403)\n      }\n      const location = response.headers.get('location')\n      if (location) {\n        fileURL = new URL(location, fileURL).href\n        if (hasAllowList && !isURLAllowed(fileURL, config.upload.pasteURL.allowList)) {\n          throw new APIError('The provided URL is not allowed.', 400)\n        }\n        continue\n      }\n    }\n\n    break\n  }\n\n  if (!response.ok) {\n    throw new APIError('Failed to fetch the file from the provided URL.', response.status)\n  }\n","sourceCodeStart":75,"sourceCodeEnd":111,"githubUrl":"https://github.com/payloadcms/payload/blob/00c58b35c0ed348ddc22daabf467b139727214fd/packages/payload/src/uploads/endpoints/getFileFromURL.ts#L75-L111","documentation":"APIError (HTTP 403, 'Too many redirects.') thrown when following the redirect chain from the remote server exceeds maxRedirects (3). The handler follows 3xx responses manually (redirect: 'manual'), incrementing redirectCount each time, and aborts when it would exceed 3.","triggerScenarios":"The remote src URL (or any redirect hop) returns 3xx more than 3 times in a row — e.g. a redirect loop, a CDN chain, or a URL shortener stacking hops. Each Location header is resolved and re-fetched; on the 4th redirect the handler throws.","commonSituations":"Redirect loop on the origin (A→B→A); a chain through multiple CDNs/login-gates; an HTTP→HTTPS→www hop sequence exceeding 3; a host that redirects to a session URL that itself redirects.","solutions":["Use a direct, canonical URL with no redirects (resolve the chain once and store the final URL).","Fix the redirect loop on the origin server.","Pre-warm/resolve the URL client-side and pass the final destination as src.","If you legitimately need more hops, fork the endpoint (maxRedirects is hard-coded to 3)."],"exampleFix":"// client — before\nfetch(`/api/media/paste-url?src=${encodeURIComponent(shortUrl)}`)\n// after — resolve redirects first, send final URL\nconst finalUrl = await resolveRedirects(shortUrl, { max: 5 })\nfetch(`/api/media/paste-url?src=${encodeURIComponent(finalUrl)}`)","handlingStrategy":"retry","validationCode":"async function finalUrlAfterRedirects(u: string, max = 3): Promise<string> {\n  let cur = u, n = 0\n  while (n <= max) {\n    const r = await fetch(cur, { redirect: 'manual' })\n    if (r.status >= 300 && r.status < 400 && r.headers.get('location')) {\n      cur = new URL(r.headers.get('location')!, cur).href; n++\n    } else return cur\n  }\n  throw new Error('Too many redirects')\n}\nconst finalSrc = await finalUrlAfterRedirects(src)\n// send finalSrc as src to the endpoint","typeGuard":"const isRedirectStatus = (s: number): boolean => s >= 300 && s < 400","tryCatchPattern":"try {\n  await fetch(`/api/media/paste-url?src=${encodeURIComponent(src)}`, { method: 'POST' })\n} catch (e) {\n  if (/Too many redirects/.test(e.message)) {\n    // resolve upstream and retry once with the final URL\n    const final = await resolveRedirects(src)\n    await fetch(`/api/media/paste-url?src=${encodeURIComponent(final)}`, { method: 'POST' })\n  }\n}","preventionTips":["Prefer canonical (already-resolved) URLs as src.","Fix redirect loops on the origin server.","Resolve redirects client-side once and cache the final URL.","Remember the server caps redirects at 3 — plan hops accordingly."],"tags":["upload","paste-url","network","redirects"],"backgroundTag":null,"analyzedSha":"00c58b35c0ed348ddc22daabf467b139727214fd","analyzedAt":"2026-08-12T20:45:03.758Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}