laurent22/joplin · error · Error
Not a valid URL: ${url}
Error message
Not a valid URL: ${url} What it means
Thrown by shim.fetch (the React Native fetch wrapper) when new URL(url) throws a TypeError, i.e. the URL string is not parseable. This pre-validation exists because React Native's native fetch crashes the app with an uncatchable error on malformed URLs (see facebook/react-native#7436). The wrapper converts that into a catchable Error so callers can handle it.
Source
Thrown at packages/app-mobile/utils/shim-init-react/shimInitShared.ts:34
shim.sjclModule = require('@joplin/lib/vendor/sjcl-rn.js');
shim.stringByteLength = function(string) {
return Buffer.byteLength(string, 'utf-8');
};
shim.httpAgent = () => null;
shim.fetch = async function(url, options = null) {
// The native fetch() throws an uncatchable error that crashes the
// app if calling it with an invalid URL such as '//.resource' or
// "http://ocloud. de" so detect if the URL is valid beforehand and
// throw a catchable error. Bug:
// https://github.com/facebook/react-native/issues/7436
let validatedUrl = '';
try { // Check if the url is valid
validatedUrl = new URL(url).href;
} catch (error) { // If the url is not valid, a TypeError will be thrown
throw new Error(`Not a valid URL: ${url}`);
}
return shim.fetchWithRetry(() => {
// If the request has a body and it's not a GET call, and it
// doesn't have a Content-Type header we display a warning,
// because it could trigger a "Network request failed" error.
// https://github.com/facebook/react-native/issues/30176
if (options?.body && options?.method && options.method !== 'GET' && !options?.headers?.['Content-Type']) {
console.warn('Done a non-GET fetch call without a Content-Type header. It may make the request fail.', url, options);
}
// Among React Native `fetch()` many bugs, one of them is that
// it will truncate strings when they contain binary data.
// Browser fetch() or Node fetch() work fine but as always RN's
// one doesn't. There's no obvious way to fix this so we'll
// have to wait if it's eventually fixed upstream. See here for
// more info:
// https://github.com/laurent22/joplin/issues/3986#issuecomment-718019688View on GitHub (pinned to 2654b33620)
Solutions
- Sanitize and validate URLs at the source: new URL(candidate) before storing in settings or passing to shim.fetch.
- Trim whitespace and strip control characters from user-entered URLs.
- If a relative URL is intended, supply a base: new URL(relative, base).href.
- Check the sync target configuration in Settings for stray characters.
Example fix
// before
await shim.fetch(userInputUrl, { method: 'GET' });
// after — validate first, give the user feedback
try {
new URL(userInputUrl);
} catch {
throw new Error(`Please enter a valid URL (got: ${userInputUrl})`);
}
await shim.fetch(userInputUrl.trim(), { method: 'GET' }); Defensive patterns
Strategy: validation
Validate before calling
function assertValidUrl(candidate) {
if (typeof candidate !== 'string') throw new Error('URL must be a string');
const trimmed = candidate.trim();
try { new URL(trimmed); return trimmed; }
catch { throw new Error(`Not a valid URL: ${candidate}`); }
}
const safe = assertValidUrl(url);
await shim.fetch(safe, options); Type guard
function isValidUrl(s) {
if (typeof s !== 'string') return false;
try { new URL(s.trim()); return true; } catch { return false; }
} Try / catch
try {
await shim.fetch(url, options);
} catch (e) {
if (/Not a valid URL/.test(e.message)) {
// prompt user to fix the URL in settings
} else throw e;
} Prevention
- Validate URLs at the input boundary (settings form, import path) with new URL().
- Trim whitespace and strip control characters from user-entered URLs.
- Never pass undefined/null/relative URLs without a base.
When it happens
Trigger: Passing '//' or '//.resource'; a URL with an illegal space such as 'http://ocloud. de'; undefined/null/non-string coerced to a bad string; a relative URL with no base; a URL with embedded control characters; a sync target misconfigured in settings.
Common situations: A resource or sync URL loaded from user settings contains a typo or trailing space; an attachment URL from a server response is malformed; clipboard/imported data injected an invalid URL; a proxy URL env var is empty producing '//'.
Related errors
- Could not download from ${modelUrl}: Error ${response.status
- fetchBlob: ${method} ${url}: ${error.toString()}
- uploadBlob: ${method} ${url}: ${error.toString()}
- fetch failed: ${result.statusText}
- Could not check for updates. The server rate limit has been
AI-assisted analysis of laurent22/joplin@2654b33620 (2026-08-12).
Data as JSON: /api/errors/1a7b50147c784db2.
Report an issue: GitHub.