remotion-dev/remotion · error · Error
Could not download remote asset
Error message
Could not download remote asset: ${response.status} What it means
Thrown by downloadRemoteAsset in @remotion/browser-studio when the fetch completed but `response.ok` is false — the server answered with an HTTP error status (404, 403, 500, ...). The status code is included in the message. The promise rejects directly, so callers must catch it.
Solutions
- Open the URL directly in a browser tab and confirm it returns the image with a 200
- For 403/410 on signed URLs, generate a fresh signed URL and retry
- For 404, fix the path — the asset moved or was deleted
- For 5xx/429, wait and retry later or mirror the asset to a reliable host
Example fix
// before
await operations.downloadRemoteAsset({url}); // rejects: Could not download remote asset: 404
// after
try {
await operations.downloadRemoteAsset({url});
} catch (e) {
const m = e instanceof Error && e.message.match(/Could not download remote asset: (\d+)/);
if (m) {
const status = Number(m[1]);
if (status === 404) throw new Error('That link no longer exists — check the URL');
if (status === 403) throw new Error('The host denied access — the link may have expired');
}
throw e;
} Defensive patterns
Strategy: try-catch
Validate before calling
const check = await fetch(url, {method: 'HEAD'});
if (!check.ok) throw new Error(`Asset URL returned ${check.status}`); Try / catch
try {
await operations.downloadRemoteAsset({url});
} catch (error) {
const m = error instanceof Error ? error.message.match(/Could not download remote asset: (\d+)/) : null;
if (m) {
const status = Number(m[1]);
if (status === 404 || status === 410) throw new Error('Link is dead — the asset was moved or deleted');
if (status === 403) throw new Error('Access denied — the link may be expired or signed incorrectly');
throw new Error(`Origin server error (${status}) — try again later`);
}
throw error;
} Prevention
- Validate pasted links with a HEAD request before adding them to a project
- Regenerate signed URLs at import time rather than persisting them
- For 5xx/429 responses, back off before retrying
When it happens
Trigger: Calling `downloadRemoteAsset({url})` for a deleted/expired link (404), a signed URL past its expiry or with a bad signature (403), a rate-limited or broken origin (429/5xx), or a URL that returns HTML error pages instead of an image.
Common situations: Pasted links from expiring share URLs; hotlink-protected hosts; CDN URLs that require a token; typos in paths.
Related errors
- Could not fetch remote asset. The URL may not allow…
- Timed out downloading remote asset
- Could not download
- Could not read /
- Failed to fetch (HTTP code: )
AI-assisted analysis of remotion-dev/remotion@10db9de073 (2026-08-22).
Data as JSON: /api/errors/c5fe5ba8ec3f698f.
Report an issue: GitHub.
Appendix: source
Thrown at packages/browser-studio/src/download-remote-asset.ts:71
try {
response = await fetch(url, {
headers: {accept: remoteAssetAcceptHeader},
signal: abortController.signal,
});
} catch (error) {
if (error instanceof Error && error.name === 'AbortError') {
throw new Error('Timed out downloading remote asset');
}
throw new Error(
`Could not fetch remote asset. The URL may not allow cross-origin requests (CORS): ${
error instanceof Error ? error.message : String(error)
}`,
);
}
if (!response.ok) {
throw new Error(`Could not download remote asset: ${response.status}`);
}
const contentLength = response.headers.get('content-length');
if (contentLength !== null && Number(contentLength) > maxRemoteAssetSize) {
abortController.abort();
throw new Error('Remote asset exceeds the 50MB size limit');
}
if (!response.body) {
const buffer = await response.arrayBuffer();
if (buffer.byteLength > maxRemoteAssetSize) {
throw new Error('Remote asset exceeds the 50MB size limit');
}
contents = new Uint8Array(buffer);
} else {
const reader = response.body.getReader();
const chunks: Uint8Array[] = [];View on GitHub (pinned to 10db9de073)