Skip to main content

Errors

Errors are an inevitable fact of software development. SvelteKit handles errors differently depending on where they occur, what kind of errors they are, and the nature of the incoming request.

Error objects

Every error passes through the handleError hook — which can log it and customise it — before it is rendered. The hook's kind property identifies where the error came from: your app ('app'), SvelteKit ('framework'), validation of a remote function argument ('validation') or an unknown source ('unknown'). By default, all are represented as simple { status: number, message: string } objects.

You can add additional properties, like a code or a tracking id, as shown in the examples below. (When using TypeScript this requires you to redefine the Error type as described in Type safety below).

App errors

An app error is one thrown from your app code using the error function imported from @sveltejs/kit:

src/routes/blog/[slug]/+page.server
import { 
function error(status: {
    status: number;
    message: string;
} extends App.Error ? number : never, message?: string | undefined): never (+2 overloads)

Throws an error with a HTTP status code and an optional message. When called during request handling, this will cause SvelteKit to return an error response; the error will be passed to handleError as an expected error. Make sure you're not catching the thrown error, which would prevent SvelteKit from handling it.

@param
status The HTTP status code. Must be in the range 400-599.
@param
message The error message.
@throws
import('./public.js').HttpError This error instructs SvelteKit to initiate HTTP error handling.
@throws
Error If the provided status is invalid (not between 400 and 599).
error
} from '@sveltejs/kit';
import * as module "#lib/server/database.js"db from '#lib/server/database.js'; /** @type {import('./$types').PageServerLoad} */ export async function function load(event: ServerLoadEvent<Record<string, any>, Record<string, any>, string | null>): MaybePromise<void | Record<string, any>>load({ params: Record<string, any>

The parameters of the current route - e.g. for a route like /blog/[slug], a { slug: string } object.

Inside query functions (including query.batch and query.live), accessing this property throws an error. Pass values from the page as arguments to the query instead. Inside form and command functions it relates to the page the remote function was called from, not the URL of the endpoint SvelteKit creates for the remote function. Never use it to determine whether or not a user is authorized to access certain data, as these values are part of the request which could be manipulated.

params
}) {
const
const post: {
    title: string;
    content: string;
} | undefined
post
= await module "#lib/server/database.js"db.
function getPost(slug: string): Promise<{
    title: string;
    content: string;
} | undefined>
getPost
(params: Record<string, any>

The parameters of the current route - e.g. for a route like /blog/[slug], a { slug: string } object.

Inside query functions (including query.batch and query.live), accessing this property throws an error. Pass values from the page as arguments to the query instead. Inside form and command functions it relates to the page the remote function was called from, not the URL of the endpoint SvelteKit creates for the remote function. Never use it to determine whether or not a user is authorized to access certain data, as these values are part of the request which could be manipulated.

params
.slug);
if (!
const post: {
    title: string;
    content: string;
} | undefined
post
) {
function error(status: {
    status: number;
    message: string;
} extends App.Error ? number : never, message?: string | undefined): never (+2 overloads)

Throws an error with a HTTP status code and an optional message. When called during request handling, this will cause SvelteKit to return an error response; the error will be passed to handleError as an expected error. Make sure you're not catching the thrown error, which would prevent SvelteKit from handling it.

@param
status The HTTP status code. Must be in the range 400-599.
@param
message The error message.
@throws
import('./public.js').HttpError This error instructs SvelteKit to initiate HTTP error handling.
@throws
Error If the provided status is invalid (not between 400 and 599).
error
(404, 'Not found');
} return {
post: {
    title: string;
    content: string;
}
post
};
}
import { 
function error(status: {
    status: number;
    message: string;
} extends App.Error ? number : never, message?: string | undefined): never (+2 overloads)

Throws an error with a HTTP status code and an optional message. When called during request handling, this will cause SvelteKit to return an error response; the error will be passed to handleError as an expected error. Make sure you're not catching the thrown error, which would prevent SvelteKit from handling it.

@param
status The HTTP status code. Must be in the range 400-599.
@param
message The error message.
@throws
import('./public.js').HttpError This error instructs SvelteKit to initiate HTTP error handling.
@throws
Error If the provided status is invalid (not between 400 and 599).
error
} from '@sveltejs/kit';
import * as module "#lib/server/database.js"db from '#lib/server/database.js'; import type { type PageServerLoad = (event: ServerLoadEvent<Record<string, any>, Record<string, any>, string | null>) => MaybePromise<void | Record<string, any>>PageServerLoad } from './$types'; export const const load: PageServerLoadload: type PageServerLoad = (event: ServerLoadEvent<Record<string, any>, Record<string, any>, string | null>) => MaybePromise<void | Record<string, any>>PageServerLoad = async ({ params: Record<string, any>

The parameters of the current route - e.g. for a route like /blog/[slug], a { slug: string } object.

Inside query functions (including query.batch and query.live), accessing this property throws an error. Pass values from the page as arguments to the query instead. Inside form and command functions it relates to the page the remote function was called from, not the URL of the endpoint SvelteKit creates for the remote function. Never use it to determine whether or not a user is authorized to access certain data, as these values are part of the request which could be manipulated.

params
}) => {
const
const post: {
    title: string;
    content: string;
} | undefined
post
= await module "#lib/server/database.js"db.
function getPost(slug: string): Promise<{
    title: string;
    content: string;
} | undefined>
getPost
(params: Record<string, any>

The parameters of the current route - e.g. for a route like /blog/[slug], a { slug: string } object.

Inside query functions (including query.batch and query.live), accessing this property throws an error. Pass values from the page as arguments to the query instead. Inside form and command functions it relates to the page the remote function was called from, not the URL of the endpoint SvelteKit creates for the remote function. Never use it to determine whether or not a user is authorized to access certain data, as these values are part of the request which could be manipulated.

params
.slug);
if (!
const post: {
    title: string;
    content: string;
} | undefined
post
) {
function error(status: {
    status: number;
    message: string;
} extends App.Error ? number : never, message?: string | undefined): never (+2 overloads)

Throws an error with a HTTP status code and an optional message. When called during request handling, this will cause SvelteKit to return an error response; the error will be passed to handleError as an expected error. Make sure you're not catching the thrown error, which would prevent SvelteKit from handling it.

@param
status The HTTP status code. Must be in the range 400-599.
@param
message The error message.
@throws
import('./public.js').HttpError This error instructs SvelteKit to initiate HTTP error handling.
@throws
Error If the provided status is invalid (not between 400 and 599).
error
(404, 'Not found');
} return {
post: {
    title: string;
    content: string;
}
post
};
};

This throws an exception that SvelteKit catches, causing it to set the response status code to 404 and render an +error.svelte component, where the error is an App.Error object with the provided status and message.

On its way there, the error passes through the handleError hook with kind: 'app'. Since the shape of the error is determined by your app, it is considered safe to expose, and the hook can pass it through unchanged.

src/routes/+error
<script>
	let { error } = $props();
</script>

<h1>{error.message}</h1>
<script lang="ts">
	let { error } = $props();
</script>

<h1>{error.message}</h1>

You can add extra properties to the error object if needed:

function error(status: number, message: string, properties: keyof Omit<App.Error, "status" | "message"> extends never ? never : Omit<App.Error, "status" | "message">): never (+2 overloads)

Throws an error with a HTTP status code and an optional message. When called during request handling, this will cause SvelteKit to return an error response; the error will be passed to handleError as an expected error. Make sure you're not catching the thrown error, which would prevent SvelteKit from handling it.

@param
status The HTTP status code. Must be in the range 400-599.
@param
message The error message.
@param
properties Additional properties of the App.Error type.
@throws
import('./public.js').HttpError This error instructs SvelteKit to initiate HTTP error handling.
@throws
Error If the provided status is invalid (not between 400 and 599).
error
(404, 'Not found', {
code: stringcode: 'NOT_FOUND' });

In SvelteKit 1.x you had to throw the error yourself

Framework errors

Some errors are generated by SvelteKit itself rather than by your code — a request that doesn't match any route (404), a POST request to a page without actions (405), a request body that exceeds the size limit (413), and so on.

These also go through handleError, with kind: 'framework'. The error you receive is a { status, message } object whose message is a terse but safe description of what went wrong, such as 'Not Found', so it can be exposed to users as-is.

If you log errors inside handleError, remember that framework errors such as 404s are routine — you will generally want to avoid logging them.

Validation errors

Validation errors occur when a remote function is called with invalid data. When these are passed to handleError, they are accompanied by an array of issues. See Handling validation errors for more details.

Unknown errors

An unknown error is any other exception that occurs while handling a request. Since these can contain sensitive information, unknown error messages and stack traces are not exposed to users.

By default, unknown errors are printed to the console (or, in production, your server logs), while the error that is exposed to the user has a generic shape:

{ "status": 500, "message": "Internal Error" }

Unknown errors go through the handleError hook with kind: 'unknown', because SvelteKit does not know what went wrong. There you can add your own error handling, for example sending errors to a reporting service, or returning a custom error object which becomes the error prop passed to +error.svelte. The value you receive is the raw thrown value, and nothing about it is exposed unless you choose to expose it.

Anything you return overrides the defaults, so you can — for example — use the type of the thrown error to determine the HTTP status code used in the response:

src/hooks.server
// Assuming you have this ...
class class NotFoundNotFound extends var Error: ErrorConstructorError {}

/** @type {import('@sveltejs/kit/hooks').HandleServerError} */
export function 
function handleError(input: CaughtError<StandardSchemaV1<Input = unknown, Output = Input>.Issue> & {
    event: RequestEvent;
}): MaybePromise<void | AppErrorWithOptionalDefaults>
handleError
({ kind: "app" | "framework" | "unknown" | "validation"

Identifies the category and origin of the error

kind
, error: unknown

The caught error. Its type depends on kind

error
, event: RequestEvent<Record<string, string>, string | null>event }) {
if (kind: "app" | "framework" | "unknown" | "validation"

Identifies the category and origin of the error

kind
=== 'unknown') {
// ... you can do this if (error: unknown

The caught error. Its type depends on kind

error
instanceof class NotFoundNotFound) {
return { status?: number | undefinedstatus: 404, message?: string | undefinedmessage: 'Not found' }; } return { message?: string | undefinedmessage: 'Something went wrong' }; } // app and framework errors are already safe to expose return
error: App.Error | {
    status: number;
    message: string;
} | {
    status: number;
    message: string;
}

The caught error. Its type depends on kind

error
;
}
import type { 
type HandleServerError<Issue extends StandardSchemaV1.Issue = StandardSchemaV1.Issue> = (input: CaughtError<Issue> & {
    event: RequestEvent;
}) => MaybePromise<void | AppErrorWithOptionalDefaults>

The server-side handleError hook runs for every error thrown while responding to a request, except redirects.

The kind property discriminates between app errors (thrown with the error helper), framework errors (generated by SvelteKit itself, such as 404s), validation errors (caused by invalid remote function arguments) and unknown errors (thrown by your code, or code it calls).

The hook returns an object matching App.Error, in which status and message are optional — return them only to override the defaults. Omitted properties are inherited from the caught error: the body passed to error(...) for app errors, the status and safe message for framework and validation errors, and 500 / 'Internal Error' for unknown errors. Return nothing to keep the defaults entirely (if you augment App.Error with required properties, you must return those).

Make sure that this function never throws an error.

HandleServerError
} from '@sveltejs/kit/hooks';
// Assuming you have this ... class class NotFoundNotFound extends var Error: ErrorConstructorError {} export const const handleError: HandleServerErrorhandleError:
type HandleServerError<Issue extends StandardSchemaV1.Issue = StandardSchemaV1.Issue> = (input: CaughtError<Issue> & {
    event: RequestEvent;
}) => MaybePromise<void | AppErrorWithOptionalDefaults>

The server-side handleError hook runs for every error thrown while responding to a request, except redirects.

The kind property discriminates between app errors (thrown with the error helper), framework errors (generated by SvelteKit itself, such as 404s), validation errors (caused by invalid remote function arguments) and unknown errors (thrown by your code, or code it calls).

The hook returns an object matching App.Error, in which status and message are optional — return them only to override the defaults. Omitted properties are inherited from the caught error: the body passed to error(...) for app errors, the status and safe message for framework and validation errors, and 500 / 'Internal Error' for unknown errors. Return nothing to keep the defaults entirely (if you augment App.Error with required properties, you must return those).

Make sure that this function never throws an error.

HandleServerError
= ({ kind: "app" | "framework" | "unknown" | "validation"

Identifies the category and origin of the error

kind
, error: unknown

The caught error. Its type depends on kind

error
, event: RequestEvent<Record<string, string>, string | null>event }) => {
if (kind: "app" | "framework" | "unknown" | "validation"

Identifies the category and origin of the error

kind
=== 'unknown') {
// ... you can do this if (error: unknown

The caught error. Its type depends on kind

error
instanceof class NotFoundNotFound) {
return { status?: number | undefinedstatus: 404, message?: string | undefinedmessage: 'Not found' }; } return { message?: string | undefinedmessage: 'Something went wrong' }; } // app and framework errors are already safe to expose return
error: App.Error | {
    status: number;
    message: string;
} | {
    status: number;
    message: string;
}

The caught error. Its type depends on kind

error
;
};

Error boundaries

Errors that occur during load or rendering (for example inside a component's <script> block or template) bubble up to the nearest +error.svelte component. To handle errors at a more granular level, you can use a <svelte:boundary>:

<svelte:boundary>
	...
	{#snippet failed(error: App.Error)}
		<!-- error went through the `handleError` hook and is of type `App.Error` -->
		{error.message}
	{/snippet}
</svelte:boundary>

Responses

If an error occurs inside handle or inside a +server.js request handler, SvelteKit will respond with either a fallback error page or a JSON representation of the error object, depending on the request's Accept headers.

You can customise the fallback error page by adding a src/error.html file:

<!DOCTYPE html>
<html lang="en">
	<head>
		<meta charset="utf-8" />
		<title>%sveltekit.error.message%</title>
	</head>
	<body>
		<h1>My custom error page</h1>
		<p>Status: %sveltekit.status%</p>
		<p>Message: %sveltekit.error.message%</p>
	</body>
</html>

SvelteKit will replace %sveltekit.status% and %sveltekit.error.message% with their corresponding values.

If the error instead occurs inside a load function while rendering a page, SvelteKit will render the +error.svelte component nearest to where the error occurred. If the error occurs inside a load function in +layout(.server).js, the closest error boundary in the tree is an +error.svelte file above that layout (not next to it).

The exception is when the error occurs inside the root +layout.js or +layout.server.js, since the root layout would ordinarily contain the +error.svelte component. In this case, SvelteKit uses the fallback error page.

Type safety

If you're using TypeScript and need to customize the shape of errors, you can do so by declaring an App.Error interface in your app (by convention, in src/app.d.ts, though it can live anywhere that TypeScript can 'see'):

src/app.d
declare global {
	namespace App {
		interface interface App.Error

Defines the common shape of expected and unexpected errors. Expected errors are thrown using the error function. Every error passes through the handleError hooks, which must return this shape (with status and message optional, since they default to those of the caught error).

Error
{
App.Error.code: stringcode: string; App.Error.id: stringid: string; } } } export {};

This interface always includes status: number and message: string properties.

Further reading

Edit this page on GitHub llms.txt

previous next