payloadcms/payload · error · APIError
Too many redirects.
Error message
Too many redirects.
What it means
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.
Source
Thrown at packages/payload/src/uploads/endpoints/getFileFromURL.ts:93
// Allow-listed URLs bypass SSRF filtering (e.g. internal/localhost CDNs)
response = await fetch(fileURL, {
headers: { 'Accept-Encoding': 'identity' },
redirect: 'manual',
signal: AbortSignal.timeout(30_000),
})
} else {
response = await safeFetch(fileURL, {
headers: {
'Accept-Encoding': 'identity',
},
signal: AbortSignal.timeout(30_000),
})
}
if (response.status >= 300 && response.status < 400) {
redirectCount++
if (redirectCount > maxRedirects) {
throw new APIError('Too many redirects.', 403)
}
const location = response.headers.get('location')
if (location) {
fileURL = new URL(location, fileURL).href
if (hasAllowList && !isURLAllowed(fileURL, config.upload.pasteURL.allowList)) {
throw new APIError('The provided URL is not allowed.', 400)
}
continue
}
}
break
}
if (!response.ok) {
throw new APIError('Failed to fetch the file from the provided URL.', response.status)
}
View on GitHub (pinned to 00c58b35c0)
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).
Example fix
// client — before
fetch(`/api/media/paste-url?src=${encodeURIComponent(shortUrl)}`)
// after — resolve redirects first, send final URL
const finalUrl = await resolveRedirects(shortUrl, { max: 5 })
fetch(`/api/media/paste-url?src=${encodeURIComponent(finalUrl)}`) Defensive patterns
Strategy: retry
Validate before calling
async function finalUrlAfterRedirects(u: string, max = 3): Promise<string> {
let cur = u, n = 0
while (n <= max) {
const r = await fetch(cur, { redirect: 'manual' })
if (r.status >= 300 && r.status < 400 && r.headers.get('location')) {
cur = new URL(r.headers.get('location')!, cur).href; n++
} else return cur
}
throw new Error('Too many redirects')
}
const finalSrc = await finalUrlAfterRedirects(src)
// send finalSrc as src to the endpoint Type guard
const isRedirectStatus = (s: number): boolean => s >= 300 && s < 400
Try / catch
try {
await fetch(`/api/media/paste-url?src=${encodeURIComponent(src)}`, { method: 'POST' })
} catch (e) {
if (/Too many redirects/.test(e.message)) {
// resolve upstream and retry once with the final URL
const final = await resolveRedirects(src)
await fetch(`/api/media/paste-url?src=${encodeURIComponent(final)}`, { method: 'POST' })
}
} Prevention
- 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.
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- Failed to fetch the file from the provided URL.
- Too many redirects (max ${maxRedirects})
- You are not allowed to perform this action.
- Pasting from URL is not enabled for this collection.
- Request URL is missing.
AI-assisted analysis of payloadcms/payload@00c58b35c0 (2026-08-12).
Data as JSON: /api/errors/f3226eebcb1eded5.
Report an issue: GitHub.