makeplane/plane · error · Error
Failed to fetch image: ${response.statusText}
Error message
Failed to fetch image: ${response.statusText} What it means
Thrown by uploadCoverImage in the web cover-image helper when fetch(imageUrl) returns a non-2xx response. The thrown message embeds response.statusText so the HTTP failure reason is visible. This runs before the blob/MIME validation and before uploadUserAsset/uploadFileAsset, so it only covers the source-image fetch step.
Source
Thrown at apps/web/helpers/cover-image.helper.ts:214
/**
* Uploads a local static image to S3
*/
export const uploadCoverImage = async (
imageUrl: string,
uploadConfig: {
workspaceSlug?: string;
entityIdentifier: string;
entityType: EFileAssetType;
isUserAsset?: boolean;
}
): Promise<string> => {
const { workspaceSlug, entityIdentifier, entityType, isUserAsset = false } = uploadConfig;
// Fetch the local image
const response = await fetch(imageUrl);
if (!response.ok) {
throw new Error(`Failed to fetch image: ${response.statusText}`);
}
const blob = await response.blob();
// Validate it's actually an image
if (!blob.type.startsWith("image/")) {
throw new Error("Invalid file type. Please select an image.");
}
const fileName = imageUrl.split("/").pop()?.split("?")[0] || "image.jpg";
const file = new File([blob], fileName, { type: blob.type });
// Upload based on context
if (isUserAsset) {
const uploadResult = await fileService.uploadUserAsset(
{
entity_identifier: entityIdentifier,
entity_type: entityType,View on GitHub (pinned to 1c8a60f858)
Solutions
- Read response.statusText in the thrown message — it tells you 404 vs 403 vs 500.
- For Unsplash URLs, verify rate limits and that the host is reachable from the browser.
- For local_static covers, confirm the asset still exists in the served static path.
- Add CORS/proxy allowances for the image host, or proxy the image through your backend.
- Validate the URL (reachable HEAD request) before calling uploadCoverImage.
Example fix
// before
const response = await fetch(imageUrl);
if (!response.ok) throw new Error(`Failed to fetch image: ${response.statusText}`);
// after: include status code and rethrow with cause for better triage
const response = await fetch(imageUrl);
if (!response.ok) {
throw new Error(`Failed to fetch image (${response.status}): ${response.statusText}`);
} Defensive patterns
Strategy: try-catch
Validate before calling
// HEAD-check the URL before fetching the body
const probe = await fetch(imageUrl, { method: 'HEAD' });
if (!probe.ok) throw new Error(`Image URL not reachable (${probe.status})`); Try / catch
try { await uploadCoverImage(imageUrl, uploadConfig); }
catch (e) {
if (/Failed to fetch image/.test(e.message)) showCoverImageError(e.message);
else throw e;
} Prevention
- HEAD-probe external image URLs before passing them to uploadCoverImage.
- Whitelist image hosts in CSP/CORS policy (incl. Unsplash).
- Prefer proxied/local covers over remote URLs to avoid fetch failures.
- Read response.statusText in the thrown message to triage 404 vs 403 vs 5xx.
When it happens
Trigger: The cover image URL is a local_static path that 404s (asset removed/moved); an Unsplash URL that is rate-limited (503) or blocked by CSP/CORS; an external URL whose host returns 403/404; network failure producing a non-OK response; the URL is correct but the server returned 500.
Common situations: Selecting a local static cover whose file was deleted from the bundle; Unsplash quota exceeded; corporate proxy/CORS blocking the image host; mis-typed or stale external URL in a saved page.
Related errors
- Asset upload failed. Please try again later.
- Issue not found
- Member not found
- Member not found
- Missing required fields.
AI-assisted analysis of makeplane/plane@1c8a60f858 (2026-08-12).
Data as JSON: /api/errors/a359791124f085ea.
Report an issue: GitHub.