axios/axios · error · AxiosError
ERR_BAD_REQUEST
ERR_BAD_REQUEST
Error message
Unsupported protocol ${protocol}: What it means
Rejected synchronously (before request.send) by the XHR adapter when the resolved request URL's scheme, parsed by parseProtocol, is not in `platform.protocols` — for the browser platform: http, https, file, blob, url, data. It is a preflight sanity/security check with code ERR_BAD_REQUEST that stops axios from handing arbitrary schemes (ftp:, ws:, chrome-extension:, custom app schemes) to XMLHttpRequest.
Source
Thrown at lib/adapters/xhr.js:239
reject(!cancel || cancel.type ? new CanceledError(null, config, request) : cancel);
request.abort();
done();
request = null;
};
_config.cancelToken && _config.cancelToken.subscribe(onCanceled);
if (_config.signal) {
_config.signal.aborted
? onCanceled()
: _config.signal.addEventListener('abort', onCanceled);
}
}
const protocol = parseProtocol(_config.url);
if (protocol && !platform.protocols.includes(protocol)) {
reject(
new AxiosError(
'Unsupported protocol ' + protocol + ':',
AxiosError.ERR_BAD_REQUEST,
config
)
);
done();
return;
}
// Send the request
request.send(requestData || null);
});
};
View on GitHub (pinned to c3f553c740)
Solutions
- Fix the URL: ensure it starts with http:// or https:// — log the final _config.url (baseURL + url after merge) to see what axios actually received.
- Check environment variables/config for a missing scheme (API_URL=api.example.com should be https://api.example.com).
- Use the right client for the protocol: WebSocket API for ws://, an FTP library server-side for ftp://.
- If constructing URLs dynamically, use the URL constructor (new URL(path, base)) instead of string concatenation.
Example fix
// before — missing scheme, 'localhost' parsed as the protocol
const api = axios.create({ baseURL: 'localhost:3000/api' });
// after
const api = axios.create({ baseURL: 'http://localhost:3000/api' }); Defensive patterns
Strategy: validation
Validate before calling
const SUPPORTED_PROTOCOLS = ['http:', 'https:'];
function assertSupportedUrl(input, base) {
const u = new URL(input, base);
if (!SUPPORTED_PROTOCOLS.includes(u.protocol)) {
throw new Error(`Unsupported protocol: ${u.protocol}`);
}
return u.href;
}
await axios.get(assertSupportedUrl(userProvidedUrl, location.origin)); Type guard
function isHttpUrl(value) {
try {
const u = new URL(value, typeof location !== 'undefined' ? location.origin : undefined);
return u.protocol === 'http:' || u.protocol === 'https:';
} catch {
return false;
}
} Try / catch
try {
await axios.get(url);
} catch (err) {
if (axios.isAxiosError(err) && err.code === 'ERR_BAD_REQUEST' && /Unsupported protocol/.test(err.message)) {
// URL came from untrusted input — reject it, never coerce the scheme silently
}
throw err;
} Prevention
- Validate and normalize any user- or API-supplied URL through new URL() with a protocol allowlist before passing it to axios.
- Treat this rejection as a security control: it blocks javascript:, data:, file: and similar schemes — don't work around it.
- Use baseURL on your axios instance so relative paths can never smuggle in an unexpected scheme.
- Add tests feeding hostile URLs (javascript:alert(1), file:///etc/passwd) through your URL-handling code.
When it happens
Trigger: axios.get('ftp://host/file'); axios.get('ws://host/socket') — WebSocket URLs don't belong in axios; a baseURL or interceptor producing a URL like 'chrome-extension://...' or a mobile deep-link scheme ('myapp://'); malformed URL construction yielding an accidental scheme, e.g. missing slash making 'localhost:3000/api' parse with protocol 'localhost'.
Common situations: String-concatenating baseURL + path and losing the '//' so the port is parsed as a scheme; passing WebSocket endpoints to axios instead of the WebSocket API; browser-extension development using extension:// URLs; env var containing the URL without the https:// prefix.
Related errors
AI-assisted analysis of axios/axios@c3f553c740 (2026-07-31).
Data as JSON: /data/errors/418e4a38d1d87fad.json.
Report an issue: GitHub.