payloadcms/payload · warning · APIError
Redirect target is not allowed.
Error message
Redirect target is not allowed.
What it means
For server-side URL fetching, Payload optionally restricts origins via `upload.pasteURL.allowList`. When following a redirect, the resolved `Location` URL is checked against that allow list; if it does not match, Payload throws `APIError` HTTP 400 `Redirect target is not allowed.` This prevents a redirect from sneaking a file in from an unapproved host (a common SSRF/toxic-proxy bypass).
Source
Thrown at packages/payload/src/uploads/getExternalFile.ts:85
headers,
method: 'GET',
})
}
if (res.status >= 300 && res.status < 400) {
redirectCount++
if (redirectCount > maxRedirects) {
throw new APIError(`Too many redirects (max ${maxRedirects})`, 403)
}
const location = res.headers.get('location')
if (location) {
fileURL = new URL(location, fileURL).toString()
if (
uploadConfig.pasteURL &&
uploadConfig.pasteURL.allowList &&
!isURLAllowed(fileURL, uploadConfig.pasteURL.allowList)
) {
throw new APIError('Redirect target is not allowed.', 400)
}
continue
}
}
break
}
if (!res || !res.ok) {
throw new APIError(`Failed to fetch file from ${fileURL}`, res?.status)
}
const data = await res.arrayBuffer()
return {
name: filename,
data: Buffer.from(data),
mimetype: res.headers.get('content-type') || undefined!,View on GitHub (pinned to 00c58b35c0)
Solutions
- Add the redirect target's hostname (and any differing port/protocol) to `upload.pasteURL.allowList`.
- Use the direct final URL of the file rather than a URL known to redirect.
- Broaden the allow-list entry (e.g. add the CDN's secondary host, or a pathname wildcard) while staying scoped.
- Verify the redirect is legitimate and not a malicious open-redirect on the source host.
Example fix
// before
const Media = {
slug: 'media',
upload: {
pasteURL: {
allowList: [{ hostname: 'cdn.example.com' }], // redirect goes to cdn-secondary.example.com
},
},
}
// after
const Media = {
slug: 'media',
upload: {
pasteURL: {
allowList: [
{ hostname: 'cdn.example.com' },
{ hostname: 'cdn-secondary.example.com' },
],
},
},
} Defensive patterns
Strategy: validation
Validate before calling
import { isURLAllowed } from 'payload/utilities' // or replicate the matcher
function targetAllowed(target: string, allowList: AllowList): boolean {
return isURLAllowed(target, allowList)
}
const allowList = collection.upload?.pasteURL?.allowList ?? []
if (!targetAllowed(redirectTarget, allowList)) {
// add the target host or use a direct URL
} Type guard
function matchesAllowList(url: string, list: AllowList[]): boolean {
try {
const u = new URL(url)
return list.some((e) => e.hostname === u.hostname &&
(!e.port || e.port === u.port) &&
(!e.protocol || e.protocol === u.protocol.replace(':', '')))
} catch { return false }
} Try / catch
try {
await payload.update({ collection: 'media', id, data: { url } })
} catch (err) {
if (err instanceof Error && /redirect target is not allowed/i.test(err.message)) {
// broaden upload.pasteURL.allowList with the redirect host, or switch to a direct URL
} else throw err
} Prevention
- Include every host a paste-URL provider may redirect to in `allowList`.
- Prefer direct, stable URLs over redirecting ones.
- Keep allow-list entries scoped (hostname + port + protocol).
When it happens
Trigger: `upload.pasteURL.allowList` is configured, the fetched URL returns a 3xx, and the `Location` header resolves to a host/path/port/protocol not matching any allow-list entry (entries match `hostname`, optional `pathname`, `port`, `protocol`, `search`).
Common situations: An allowed CDN redirects to an internal/secondary host not in the list. The provider moved to a new domain. The allow list was set too narrowly (hostname only, but the redirect lands on a different port/path). `isURLAllowed` matched the initial URL but not the redirect target.
Related errors
- Blocked unsafe attempt to ${hostname}
- File type ${mimeTypeFromExtension} (from extension ${typeFro
- Invalid filename.
- The provided URL is not allowed.
- Too many redirects.
AI-assisted analysis of payloadcms/payload@00c58b35c0 (2026-08-12).
Data as JSON: /api/errors/d6e59f725f3b4dc8.
Report an issue: GitHub.