TryGhost/Ghost · error

Download failed: ${response.status} ${response.statusText}

Error message

Download failed: ${response.status} ${response.statusText}

What it means

A plain Error thrown by blobDownload() when the fetch returns a non-2xx status. The message embeds the HTTP status and statusText for diagnosis. Unlike the handleResponse pathway, blobDownload uses a raw fetch (helpers.ts:84) with no retry, no typed error class, and no explicit credentials option — so auth/cookie and proxy failures surface directly as this string error.

Source

Thrown at apps/admin-x-framework/src/utils/helpers.ts:87

        return unquotedMatch[1].trim();
    }

    return undefined;
}

/**
 * Downloads a file by fetching it as a blob and triggering a browser download.
 * Use this instead of downloadFile/downloadFromEndpoint for streaming responses
 * (e.g. large CSV exports) where the iframe approach may not work reliably.
 *
 * The filename comes from the response's `Content-Disposition` header;
 * `fallbackFilename` is only used when the server omits it.
 */
export async function blobDownload(url: string, fallbackFilename?: string): Promise<void> {
    const response = await fetch(url, {method: 'GET'});

    if (!response.ok) {
        throw new Error(`Download failed: ${response.status} ${response.statusText}`);
    }

    const filename = getFilenameFromContentDisposition(response.headers.get('content-disposition'))
        ?? fallbackFilename
        ?? 'download';

    const blob = await response.blob();
    const blobUrl = window.URL.createObjectURL(blob);
    const a = document.createElement('a');

    a.href = blobUrl;
    a.download = filename;
    document.body.appendChild(a);
    a.click();
    a.remove();
    window.URL.revokeObjectURL(blobUrl);
}

View on GitHub (pinned to 47d8b0e2ad)

Solutions

  1. Inspect the embedded status: 401/403 → re-authenticate; 404 → verify the endpoint path; 5xx → check server/proxy.
  2. For cross-origin downloads, ensure credentials are sent (same-origin, or pass credentials:'include' if the helper is extended).
  3. Catch the error and surface the status to the user instead of a generic download-failed message.

Example fix

// before: opaque failure
try { await blobDownloadFromEndpoint('/members/download/'); }
catch (e) { alert('Download failed'); }

// after: parse the embedded status to guide the user
import {blobDownloadFromEndpoint} from '@tryghost/admin-x-framework/utils/helpers';
try { await blobDownloadFromEndpoint('/members/download/'); }
catch (e: any) {
    const status = Number(e?.message?.match(/Download failed:\s*(\d+)/)?.[1]);
    if (status === 401) { redirectToSignin(); return; }
    alert(e.message);
}
Defensive patterns

Strategy: try-catch

Validate before calling

// Confirm the endpoint and session are valid before streaming a download.
async function canDownload(url: string): Promise<boolean> {
    const probe = await fetch(url, {method: 'HEAD', credentials: 'same-origin'});
    return probe.ok;
}

Try / catch

import {blobDownloadFromEndpoint} from '@tryghost/admin-x-framework/utils/helpers';
try {
    await blobDownloadFromEndpoint('/members/download/');
} catch (e: any) {
    const status = Number(e?.message?.match(/Download failed:\s*(\d+)/)?.[1]);
    if (status === 401 || status === 403) {
        redirectToSignin();
    } else if (status) {
        notify(`Download failed (${status}).`);
    } else {
        throw e;
    }
}

Prevention

When it happens

Trigger: Calling blobDownload/blobDownloadFromEndpoint for a CSV export or other download whose URL returns non-2xx: 401 (session expired, cookie not sent), 403, 404, or a 5xx/gateway error. Because no credentials option is set, cross-origin downloads may lose the session cookie.

Common situations: Exporting members/posts as CSV after the session lapsed; cross-origin download URL where the cookie isn't sent by default; the export endpoint returns 404 after a route change; a proxy blocks the large streaming response.

Related errors


AI-assisted analysis of TryGhost/Ghost@47d8b0e2ad (2026-08-13). Data as JSON: /api/errors/163a38a122378560. Report an issue: GitHub.