laurent22/joplin · error · JoplinError
fetch failed: ${result.statusText}
Error message
fetch failed: ${result.statusText} What it means
Thrown by the web-platform shim.fetchBlob when the browser fetch() resolves with result.ok === false (HTTP status >= 400). Unlike the mobile variant, this constructs a JoplinError carrying result.status as the error code, with the message 'fetch failed: <statusText>'. Used when running Joplin's web build (e.g. the browser/Electron-web target).
Source
Thrown at packages/app-mobile/utils/shim-init-react/index.web.ts:60
return fsDriver_;
};
shim.fsDriver = fsDriver;
shim.crypto = joplinCrypto;
shim.randomBytes = async (count: number) => {
const buffer = new Uint8Array(count);
crypto.getRandomValues(buffer);
return [...buffer];
};
shim.fetchBlob = async function(url, options: FetchBlobOptions) {
const outputPath = options.path;
if (!outputPath) throw new Error('fetchBlob: Missing outputPath');
const result = await fetch(url, { method: options.method, headers: options.headers });
if (!result.ok) {
throw new JoplinError(`fetch failed: ${result.statusText}`, result.status);
}
const blob = await result.blob();
await fsDriver().writeFile(outputPath, await blob.arrayBuffer(), 'Buffer');
return {
ok: result.ok,
path: outputPath,
text: () => {
return result.statusText;
},
json: () => {
return { message: `${result.status}: ${result.statusText}` };
},
status: result.status,
headers: result.headers,
};
};View on GitHub (pinned to 2654b33620)
Solutions
- Inspect the JoplinError.code (it is result.status) to branch on 401 (re-auth), 404 (missing), 429 (back off), 5xx (retry).
- Verify the URL is correct and the resource exists server-side.
- Check CORS headers on the response if running in a browser — a CORS failure may manifest as a network error or non-ok response.
- Retry with backoff for 5xx and 429; do not retry 4xx (except 408/429).
Example fix
// before
await shim.fetchBlob(url, { path, method: 'GET' });
// after — branch on the carried status code
try {
await shim.fetchBlob(url, { path, method: 'GET' });
} catch (e) {
if (e instanceof JoplinError && e.code === 401) await refreshToken();
else if (e instanceof JoplinError && e.code >= 500) await retryWithBackoff();
else throw e;
} Defensive patterns
Strategy: try-catch
Validate before calling
try { new URL(url); } catch { throw new Error(`Invalid URL: ${url}`); }
await shim.fetchBlob(url, { path, method: 'GET' }); Type guard
function isJoplinError(e) { return e && typeof e.code === 'number'; } Try / catch
try {
await shim.fetchBlob(url, { path, method: 'GET' });
} catch (e) {
if (e instanceof JoplinError) {
if (e.code === 401) await refreshToken();
else if (e.code === 429 || e.code >= 500) await retryWithBackoff();
else throw e;
} else throw e;
} Prevention
- Branch on JoplinError.code for status-specific handling.
- Verify CORS configuration on the sync target for browser builds.
- Do not retry deterministic 4xx errors (except 408/429).
When it happens
Trigger: Server returns 4xx/5xx (404 for missing resource, 401/403 auth, 500 server error, 502/503 gateway); CORS preflight rejection surfaces as a non-ok response; the sync endpoint moved and returns 404; rate limiting (429).
Common situations: Web build downloading an attachment the server cannot find; auth cookie/token expired; CORS misconfiguration on the sync target; transient 5xx during server maintenance; proxy returning 502.
Related errors
- Not a valid URL: ${url}
- Could not check for updates. The server rate limit has been
- Could not check for updates. Please try again later (Error $
- Could not download from ${modelUrl}: Error ${response.status
- Missing read-write access. It might be necessary to share th
AI-assisted analysis of laurent22/joplin@2654b33620 (2026-08-12).
Data as JSON: /api/errors/c011f277a6cf74ce.
Report an issue: GitHub.