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

  1. Fix the URL to include a valid scheme and host (e.g. prepend 'https://').
  2. Validate with `new URL(url)` in a try-catch before calling fetch.
  3. Check where the URL comes from (setting/env/arg) and fix the source value.
  4. 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

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


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)