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} eventView on GitHub (pinned to 03f1687fe6)
Solutions
- Change `throw fail(...)` to `return fail(...)` in the action and propagate the value up through helpers.
- If a helper produces the failure, return it up the call chain instead of throwing.
- Use `error(...)` from '@sveltejs/kit' for throwable errors, reserving fail() for form validation results.
- 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
- Never `throw fail(...)`; always `return fail(...)` and propagate upward.
- Reserve `throw` for error() HttpErrors.
- Make helpers return failures rather than throwing them.
- Run type checking to catch ActionFailure leaking into throw paths.
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
- exports is not available in dev mode
- The `generateManifest` adapter API has been removed — use `g
- Instrumentation file ${instrumentation} not found. This is p
- Entrypoint file ${entrypoint} not found. This is probably a
- Instrumentation initializer ${initializer} not found. This i
AI-assisted analysis of sveltejs/kit@03f1687fe6 (2026-09-02).
Data as JSON: /api/errors/cd208a7ca1973037.
Report an issue: GitHub.