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

  1. 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.
  2. Check environment variables/config for a missing scheme (API_URL=api.example.com should be https://api.example.com).
  3. Use the right client for the protocol: WebSocket API for ws://, an FTP library server-side for ftp://.
  4. 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

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.