sveltejs/kit · error · Error

Cannot redirect to external URL ${JSON.stringify(location)}.

Error message

Cannot redirect to external URL ${JSON.stringify(location)}. To redirect to an external URL, pass `{ external: true }` or an allowlist of permitted origins as the third argument to `redirect` (prod: 'Cannot redirect to external URL unless explicitly allowed')

What it means

`redirect()` refuses external (absolute cross-origin) URLs by default. To redirect off-site you must explicitly opt in with `{ external: true }` or pass an allowlist of permitted origins as the third argument, protecting against open-redirect vulnerabilities. In production the message is intentionally vague to avoid leaking validation details.

Source

Thrown at packages/kit/src/exports/url.js:48

function is_javascript_location(location) {
	try {
		return javascript_protocols.has(new URL(location, REDIRECT_BASE).protocol);
	} catch {
		return false;
	}
}

/**
 * @param {string} location
 * @param {{ external?: boolean | string[] }} [options]
 */
export function validate_redirect_location(location, options) {
	if (!is_external_location(location)) return;

	const external = options?.external;

	if (!external) {
		throw new Error(
			DEV
				? `Cannot redirect to external URL ${JSON.stringify(location)}. ` +
						'To redirect to an external URL, pass `{ external: true }` or an allowlist of permitted origins as the third argument to `redirect`'
				: 'Cannot redirect to external URL unless explicitly allowed'
		);
	}

	if (external === true) {
		if (is_javascript_location(location)) {
			throw new Error(
				DEV
					? `Cannot redirect to ${JSON.stringify(location)} with \`{ external: true }\`. ` +
							'The `javascript:` and `data:` protocols must be explicitly listed in the `external` allowlist'
					: 'Cannot redirect to external URL unless explicitly allowed'
			);
		}

		return;

View on GitHub (pinned to 03f1687fe6)

Solutions

  1. If the target is truly external, call `redirect(status, location, { external: true })`.
  2. Better: pass an allowlist of permitted origins as the third argument, e.g. `redirect(302, url, { external: ['https://partner.com'] })`.
  3. If the target should be internal, strip the origin: use `new URL(location).pathname + new URL(location).search` before redirecting.
  4. Never derive the redirect target directly from untrusted input without allowlisting.

Example fix

// before
redirect(302, event.url.searchParams.get('next'));
// after
const next = event.url.searchParams.get('next') ?? '/';
redirect(302, next, { external: ['https://trusted.example.com'] });
Defensive patterns

Strategy: validation

Validate before calling

const allowedExternalOrigins = ['https://trusted.example.com'];
function canRedirect(loc, opts) {
  const u = new URL(loc, 'http://internal');
  const isExternal = u.origin !== 'http://internal';
  if (!isExternal) return true;
  return Array.isArray(opts?.external)
    ? opts.external.includes(u.origin)
    : opts?.external === true;
}
if (!canRedirect(target, opts)) target = '/';

Type guard

const isExternalUrl = (s) => {
  try { return new URL(s, 'http://internal').origin !== 'http://internal'; } catch { return false; }
};

Try / catch

try {
  redirect(302, target);
} catch (e) {
  if (String(e.message).includes('external URL')) {
    redirect(302, '/'); // or re-redirect with allowlist
  } else throw e;
}

Prevention

When it happens

Trigger: Calling `redirect(302, 'https://other-site.com/path')` without options; redirecting to an absolute URL built from query params like `?next=https://evil.com` without `{ external: true }` or an origins allowlist.

Common situations: Post-login `?redirectTo=` flows pointing at absolute URLs; integrating OAuth return URLs; environment-based redirects where a base URL is absolute (e.g. `https://app.example.com/dashboard`) even in same-origin deployments.

Related errors


AI-assisted analysis of sveltejs/kit@03f1687fe6 (2026-09-02). Data as JSON: /api/errors/542c8015a439ba52. Report an issue: GitHub.