laurent22/joplin · error
Not a valid URL
Error message
Not a valid URL: ${url} What it means
shim.fetch wraps network requests and pre-validates the URL with `new URL(url)`. If URL parsing throws a TypeError, the fetch is aborted with a clearer 'Not a valid URL' message rather than an opaque parser error.
Solutions
- Fix the URL to include a valid scheme and host (e.g. prepend 'https://').
- Validate with `new URL(url)` in a try-catch before calling fetch.
- Check where the URL comes from (setting/env/arg) and fix the source value.
- Log the raw input value to spot empty or truncated URLs.
Example fix
// before await shim.fetch(config.serverUrl); // after const url = new URL(config.serverUrl || 'https://localhost:22300'); await shim.fetch(url.toString());
Defensive patterns
Strategy: validation
Validate before calling
function isValidUrl(u: string): boolean { try { new URL(u); return true; } catch { return false; } }
if (isValidUrl(url)) await shim.fetch(url); Type guard
const isUrl = (u: unknown): u is string => typeof u === 'string' && u.length > 0 && (() => { try { new URL(u); return true; } catch { return false; } })(); Try / catch
try { await shim.fetch(url, opts); } catch (e) { if (String(e.message).startsWith('Not a valid URL:')) throw new Error(`Check server URL setting: ${e.message}`); else throw e; } Prevention
- Normalize user-provided URLs (trim, prepend scheme) before storing
- Validate URL settings at save time, not fetch time
- Never pass empty/undefined strings into fetch
When it happens
Trigger: Calling shim.fetch (or APIs built on it, like resource downloads or sync/upgrade checks) with a malformed, empty, or non-absolute URL string — e.g. missing protocol ('example.com/api'), whitespace, or undefined coerced to a string.
Common situations: Config files with a missing/typo'd server URL (e.g. Joplin Server URL without https://); relative URLs passed to fetch in Node/Electron context; empty string settings read before configuration completes.
Understand the failure class
Background: "Invalid URL" errors: why new URL(), URI.parse, and reqwest::Url reject your string — missing scheme, whitespace, and bad path format — this error's family across 39 libraries.
Related errors
- fetch failed
- Not a valid URL
- All potential ports are in use or not available.
- Ambiguous notebook " ". Please use notebook id instead -…
- Ambiguous notebook " ". Please use short notebook id…
AI-assisted analysis of laurent22/joplin@981a03c5c9 (2026-09-17).
Data as JSON: /api/errors/ef20bed404c69cd6.
Report an issue: GitHub.
Appendix: source
Thrown at packages/lib/shim-init-node.ts:552
const imageDataURI = require('image-data-uri');
const result = imageDataURI.decode(imageDataUrl);
await shim.fsDriver().writeFile(filePath, result.dataBuffer, 'buffer');
}
};
const nodeFetch = require('node-fetch');
// Not used??
shim.readLocalFileBase64 = path => {
const data = fs.readFileSync(path);
return new Buffer(data).toString('base64');
};
shim.fetch = async function(url, options = {}) {
try { // Check if the url is valid
new URL(url);
} catch (error) { // If the url is not valid, a TypeError will be thrown
throw new Error(`Not a valid URL: ${url}`);
}
const resolvedProxyUrl = resolveProxyUrl(proxySettings.proxyUrl);
if (resolvedProxyUrl && proxySettings.proxyEnabled) {
options.agent = shim.proxyAgent(url, resolvedProxyUrl);
} else {
// node-fetch calls this for every request, including each redirect hop, so the agent
// always matches the protocol actually being used.
const agents = shim.httpAgents();
options.agent = (parsedUrl: URL) => parsedUrl.protocol === 'https:' ? agents.https : agents.http;
}
return shim.fetchWithRetry(() => {
return nodeFetch(url, options);
}, options);
};
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- url is passed as a string but reassigned to a parsed UrlWithStringQuery by urlParse below
shim.fetchBlob = async function(url: any, options: FetchBlobOptions) {
if (!options || !options.path) throw new Error('fetchBlob: target file path is missing');View on GitHub (pinned to 981a03c5c9)