sveltejs/kit · error · Error

Cannot "throw fail()". Use "return fail()"

Error message

Cannot "throw fail()". Use "return fail()"

What it means

fail() in SvelteKit actions returns a special ActionFailure object that must be returned from the action, not thrown. If an action throws an ActionFailure (e.g. `throw fail(400, ...)`), handle_action_request replaces it with this Error to tell the developer they used the wrong control flow. Only HttpError (from error()) may be thrown.

Source

Thrown at packages/kit/src/runtime/server/page/actions.js:198

		if (data instanceof ActionFailure) {
			return {
				type: 'failure',
				status: data.status,
				location,
				data: data.data
			};
		} else {
			return {
				type: 'success',
				status: 200,
				location,
				// @ts-expect-error this will be removed upon serialization, so `undefined` is the same as omission
				data
			};
		}
	} catch (e) {
		return action_error_result(
			e instanceof ActionFailure ? new Error('Cannot "throw fail()". Use "return fail()"') : e,
			location
		);
	}
}

/**
 * @param {Actions} actions
 */
function check_named_default_separate(actions) {
	if (actions.default && Object.keys(actions).length > 1) {
		throw new Error(
			'When using named actions, the default action cannot be used. See the docs for more info: https://svelte.dev/docs/kit/form-actions#named-actions'
		);
	}
}

/**
 * @param {RequestEvent} event

View on GitHub (pinned to 03f1687fe6)

Solutions

  1. Change `throw fail(...)` to `return fail(...)` in the action and propagate the value up through helpers.
  2. If a helper produces the failure, return it up the call chain instead of throwing.
  3. Use `error(...)` from '@sveltejs/kit' for throwable errors, reserving fail() for form validation results.
  4. Run `pnpm run check`/tsc to surface paths where an ActionFailure leaks into a throw.

Example fix

// before
import { fail } from '@sveltejs/kit';
if (!data.email) throw fail(400, { invalid: true });
// after
if (!data.email) return fail(400, { invalid: true });
Defensive patterns

Strategy: try-catch

Validate before calling

// helper returning instead of throwing
/** @returns {import('@sveltejs/kit').ActionFailure} */
function validate(data) {
  if (!data.email) return fail(400, { invalid: true });
  // ...
}

Type guard

function isActionFailure(e) {
  return e instanceof ActionFailure;
}

Try / catch

try {
  const result = await helper();
  if (result instanceof ActionFailure) return result; // propagate, don't throw
} catch (e) {
  if (e instanceof ActionFailure) return e;
  throw error(500, 'Unexpected failure');
}

Prevention

When it happens

Trigger: Writing `throw fail(400, { field: 'bad' })` inside a form action (or code called by it) instead of `return fail(400, { field: 'bad' })`; also happens when a helper that was written to `return fail()` is accidentally `throw`n, or after migrating habits from libraries where throwing result objects is idiomatic.

Common situations: Refactoring validation helpers where one path returns fail() and a wrapper throws it; copying error() throwing patterns into fail() usage; TypeScript not catching it because fail's throw-ability isn't statically rejected in all code paths.

Related errors


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