{"record":{"id":"bea0e8b5109b0c71","repo":"payloadcms/payload","slug":"failed-to-fetch-from-stringifiedurl-error-me","errorCode":null,"errorMessage":"Failed to fetch from ${stringifiedUrl}, ${error.message}","messagePattern":"Failed to fetch from (.+?), (.+?)","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"warning","filePath":"packages/payload/src/uploads/safeFetch.ts","lineNumber":112,"sourceCode":"      redirect: 'manual', // Prevent automatic redirects\n    })) as unknown as Response\n  } catch (error) {\n    if (error instanceof Error) {\n      if (error.cause instanceof Error && error.cause.message.includes('unsafe')) {\n        // Errors thrown from within interceptors always have 'fetch error' as the message\n        // The desired message we want to bubble up is in the cause\n        throw new Error(error.cause.message)\n      } else {\n        let stringifiedUrl: string | undefined = undefined\n        if (typeof unverifiedUrl === 'string') {\n          stringifiedUrl = unverifiedUrl\n        } else if (unverifiedUrl instanceof URL) {\n          stringifiedUrl = unverifiedUrl.toString()\n        } else if (unverifiedUrl instanceof Request) {\n          stringifiedUrl = unverifiedUrl.url\n        }\n\n        throw new Error(`Failed to fetch from ${stringifiedUrl}, ${error.message}`)\n      }\n    }\n    throw error\n  }\n}\n","sourceCodeStart":94,"sourceCodeEnd":118,"githubUrl":"https://github.com/payloadcms/payload/blob/00c58b35c0ed348ddc22daabf467b139727214fd/packages/payload/src/uploads/safeFetch.ts#L94-L118","documentation":"`safeFetch`'s fallback branch catches any fetch error that is **not** an SSRF `unsafe` block — network failures, DNS resolution errors (NXDOMAIN), TLS/cert errors, connection refused/reset, timeouts — and re-throws a plain `Error` `Failed to fetch from <stringifiedUrl>, <original message>`. The original cause is preserved in the message text (not in `error.cause`).","triggerScenarios":"An external-file or paste-URL fetch where: DNS for the hostname fails (NXDOMAIN), the connection is refused/reset, TLS handshake fails (expired/self-signed cert), the server times out, or a proxy/egress firewall blocks the connection. Distinct from a successful-but-non-2xx response (that's error 271).","commonSituations":"No/incorrect egress rules (firewall, security group, VPC). DNS misconfiguration in the container. Self-signed/expired TLS certs on the target. Target host down. A proxy is required but not configured. IPv6-only target with no IPv6 egress. Local dev hitting an unreachable hostname.","solutions":["From the server, run `curl -v <url>` / `nslookup <hostname>` to confirm reachability and TLS.","Open the required egress (host/port) in firewall/security groups; configure an HTTP(S) proxy if your network mandates one.","Fix the target's TLS cert (valid, non-expired, trusted CA) or add the CA to the trust store.","Use a resolvable, reachable hostname; avoid internal-only names in production fetches.","Retry transient network failures with bounded backoff.","Add the URL to `skipSafeFetch`/`allowList` only if you have deliberately bypassed safeFetch and still see transport errors."],"exampleFix":"// before — calling update with an unreachable remote URL\nawait payload.update({ collection: 'media', id, data: { url: 'https://files.example.com/a.png' } })\n// server cannot resolve/connect -> Failed to fetch from ...\n\n// after — verify reachability first, then store a working URL\nconst ok = await fetch('https://files.example.com/a.png').then(r => r.ok).catch(() => false)\nif (ok) {\n  await payload.update({ collection: 'media', id, data: { url: 'https://files.example.com/a.png' } })\n} else {\n  // upload the bytes directly instead of remote-fetching\n}","handlingStrategy":"retry","validationCode":"async function isReachable(url: string, timeoutMs = 5000): Promise<boolean> {\n  const ctrl = new AbortController()\n  const t = setTimeout(() => ctrl.abort(), timeoutMs)\n  try {\n    const res = await fetch(url, { method: 'HEAD', signal: ctrl.signal })\n    return res.ok\n  } catch { return false } finally { clearTimeout(t) }\n}\n\nif (!(await isReachable(doc.url))) {\n  // unreachable — fix DNS/TLS/egress or upload bytes directly\n}","typeGuard":"function isTransportError(err: unknown): boolean {\n  return err instanceof Error && /failed to fetch from/i.test(err.message) && !/unsafe/i.test(err.message)\n}","tryCatchPattern":"async function fetchWithRetry(url: string, attempts = 3) {\n  for (let i = 0; i < attempts; i++) {\n    try { return await fetch(url) }\n    catch (err) {\n      if (i === attempts - 1 || !isTransportError(err)) throw err\n      await new Promise((r) => setTimeout(r, 2 ** i * 200))\n    }\n  }\n}","preventionTips":["Verify egress (host/port), DNS, and TLS from the server before depending on remote fetch.","Configure an HTTP(S) proxy if the network requires one.","Use bounded backoff retry for transient transport errors.","Keep target TLS certs valid and trusted."],"tags":["network","external-file","dns","tls","safe-fetch"],"backgroundTag":null,"analyzedSha":"00c58b35c0ed348ddc22daabf467b139727214fd","analyzedAt":"2026-08-12T20:45:03.758Z","schemaVersion":2},"datasetVersion":"2026-08-13T04:17:16.726Z"}