From 73dac279ef24992018f0553c7e42376f0ba09d57 Mon Sep 17 00:00:00 2001 From: Justin Baum Date: Wed, 12 Nov 2025 12:16:32 -0500 Subject: [PATCH 01/12] feat(#14442): allow custom http status codes --- .changeset/seven-mangos-cheat.md | 5 +++++ documentation/docs/30-advanced/20-hooks.md | 2 +- packages/kit/src/utils/error.js | 10 +++++++++- .../basics/src/routes/errors/custom-error/+server.js | 10 ++++++++++ packages/kit/test/apps/basics/test/server.test.js | 6 ++++++ 5 files changed, 31 insertions(+), 2 deletions(-) create mode 100644 .changeset/seven-mangos-cheat.md create mode 100644 packages/kit/test/apps/basics/src/routes/errors/custom-error/+server.js diff --git a/.changeset/seven-mangos-cheat.md b/.changeset/seven-mangos-cheat.md new file mode 100644 index 000000000000..9521a15b0b36 --- /dev/null +++ b/.changeset/seven-mangos-cheat.md @@ -0,0 +1,5 @@ +--- +'@sveltejs/kit': minor +--- + +Allow Custom HTTP Status Codes diff --git a/documentation/docs/30-advanced/20-hooks.md b/documentation/docs/30-advanced/20-hooks.md index 2091e49794c7..f1a3bbd793a5 100644 --- a/documentation/docs/30-advanced/20-hooks.md +++ b/documentation/docs/30-advanced/20-hooks.md @@ -186,7 +186,7 @@ If an [unexpected error](errors#Unexpected-errors) is thrown during loading, ren - you can log the error - you can generate a custom representation of the error that is safe to show to users, omitting sensitive details like messages and stack traces. The returned value, which defaults to `{ message }`, becomes the value of `$page.error`. -For errors thrown from your code (or library code called by your code) the status will be 500 and the message will be "Internal Error". While `error.message` may contain sensitive information that should not be exposed to users, `message` is safe (albeit meaningless to the average user). +For errors thrown from your code (or library code called by your code) the status will be 500 unless overriden `error.status` and the message will be "Internal Error". While `error.message` may contain sensitive information that should not be exposed to users, `message` is safe (albeit meaningless to the average user). To add more information to the `$page.error` object in a type-safe way, you can customize the expected shape by declaring an `App.Error` interface (which must include `message: string`, to guarantee sensible fallback behavior). This allows you to — for example — append a tracking ID for users to quote in correspondence with your technical support staff: diff --git a/packages/kit/src/utils/error.js b/packages/kit/src/utils/error.js index c12b98eff41c..01c26081317f 100644 --- a/packages/kit/src/utils/error.js +++ b/packages/kit/src/utils/error.js @@ -25,9 +25,17 @@ export function normalize_error(error) { /** * @param {unknown} error + * @returns {number} */ export function get_status(error) { - return error instanceof HttpError || error instanceof SvelteKitError ? error.status : 500; + if (error instanceof HttpError || error instanceof SvelteKitError) { + return error.status; + } + // For overrides / custom error objects + if (typeof error === 'object' && error !== null && 'status' in error) { + return /** @type {{ status?: number }} */ (error).status ?? 500; + } + return 500; } /** diff --git a/packages/kit/test/apps/basics/src/routes/errors/custom-error/+server.js b/packages/kit/test/apps/basics/src/routes/errors/custom-error/+server.js new file mode 100644 index 000000000000..6f9f0422d251 --- /dev/null +++ b/packages/kit/test/apps/basics/src/routes/errors/custom-error/+server.js @@ -0,0 +1,10 @@ +class CustomError extends Error { + constructor(message, errorOpts) { + super(message, errorOpts); + this.status = 422; + } +} + +export function GET() { + throw new CustomError('Custom error'); +} diff --git a/packages/kit/test/apps/basics/test/server.test.js b/packages/kit/test/apps/basics/test/server.test.js index eea785051107..dbd842d989ff 100644 --- a/packages/kit/test/apps/basics/test/server.test.js +++ b/packages/kit/test/apps/basics/test/server.test.js @@ -452,6 +452,12 @@ test.describe('Errors', () => { expect(await response.text()).toMatch('thisvariableisnotdefined is not defined'); }); + test('custom error object', async ({ request }) => { + const response = await request.get('/errors/custom-error'); + console.log(response.status()); + expect(response.status()).toBe(422); + }); + test('returns 400 when accessing a malformed URI', async ({ page }) => { const response = await page.goto('/%c0%ae%c0%ae/etc/passwd'); if (process.env.DEV) { From d650e57f3a3f3bdb3441c9216d4fa04cfecaa9ff Mon Sep 17 00:00:00 2001 From: Justin Baum Date: Wed, 12 Nov 2025 22:48:57 -0500 Subject: [PATCH 02/12] Apply suggestions from code review Co-authored-by: Tee Ming --- .changeset/seven-mangos-cheat.md | 2 +- documentation/docs/30-advanced/20-hooks.md | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/.changeset/seven-mangos-cheat.md b/.changeset/seven-mangos-cheat.md index 9521a15b0b36..d782549a30ae 100644 --- a/.changeset/seven-mangos-cheat.md +++ b/.changeset/seven-mangos-cheat.md @@ -2,4 +2,4 @@ '@sveltejs/kit': minor --- -Allow Custom HTTP Status Codes +feat: allow returning a custom HTTP status code from the `handleError` hook diff --git a/documentation/docs/30-advanced/20-hooks.md b/documentation/docs/30-advanced/20-hooks.md index f1a3bbd793a5..07a409db0252 100644 --- a/documentation/docs/30-advanced/20-hooks.md +++ b/documentation/docs/30-advanced/20-hooks.md @@ -185,8 +185,9 @@ If an [unexpected error](errors#Unexpected-errors) is thrown during loading, ren - you can log the error - you can generate a custom representation of the error that is safe to show to users, omitting sensitive details like messages and stack traces. The returned value, which defaults to `{ message }`, becomes the value of `$page.error`. +- you can customise the status code of the response -For errors thrown from your code (or library code called by your code) the status will be 500 unless overriden `error.status` and the message will be "Internal Error". While `error.message` may contain sensitive information that should not be exposed to users, `message` is safe (albeit meaningless to the average user). +For errors thrown from your code (or library code called by your code) the status will default to 500 and the message will default to "Internal Error". While `error.message` may contain sensitive information that should not be exposed to users, `message` is safe (albeit meaningless to the average user). To add more information to the `$page.error` object in a type-safe way, you can customize the expected shape by declaring an `App.Error` interface (which must include `message: string`, to guarantee sensible fallback behavior). This allows you to — for example — append a tracking ID for users to quote in correspondence with your technical support staff: From aa9ca9ef9bc0c7bfb00451ed53625f884a4f2291 Mon Sep 17 00:00:00 2001 From: Justin Baum Date: Wed, 12 Nov 2025 22:55:26 -0500 Subject: [PATCH 03/12] Update packages/kit/test/apps/basics/test/server.test.js --- packages/kit/test/apps/basics/test/server.test.js | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/kit/test/apps/basics/test/server.test.js b/packages/kit/test/apps/basics/test/server.test.js index dbd842d989ff..b67e77efee51 100644 --- a/packages/kit/test/apps/basics/test/server.test.js +++ b/packages/kit/test/apps/basics/test/server.test.js @@ -454,7 +454,6 @@ test.describe('Errors', () => { test('custom error object', async ({ request }) => { const response = await request.get('/errors/custom-error'); - console.log(response.status()); expect(response.status()).toBe(422); }); From 59649033ff054068fd52358e770cf38c14de808b Mon Sep 17 00:00:00 2001 From: Justin Baum Date: Thu, 13 Nov 2025 11:38:42 -0500 Subject: [PATCH 04/12] Update packages/kit/src/utils/error.js Co-authored-by: Tee Ming --- packages/kit/src/utils/error.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/kit/src/utils/error.js b/packages/kit/src/utils/error.js index 01c26081317f..5f4949a3104c 100644 --- a/packages/kit/src/utils/error.js +++ b/packages/kit/src/utils/error.js @@ -32,8 +32,8 @@ export function get_status(error) { return error.status; } // For overrides / custom error objects - if (typeof error === 'object' && error !== null && 'status' in error) { - return /** @type {{ status?: number }} */ (error).status ?? 500; + if (typeof error === 'object' && error !== null && 'status' in error && typeof error.status === 'number') { + return error.status; } return 500; } From 5c3a1f02f3d9fb39baf1f9ea7e7b62733e6ebf90 Mon Sep 17 00:00:00 2001 From: Justin Baum Date: Thu, 13 Nov 2025 11:38:49 -0500 Subject: [PATCH 05/12] Update packages/kit/test/apps/basics/test/server.test.js Co-authored-by: Tee Ming --- packages/kit/test/apps/basics/test/server.test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/kit/test/apps/basics/test/server.test.js b/packages/kit/test/apps/basics/test/server.test.js index b67e77efee51..0bb27c20bb9d 100644 --- a/packages/kit/test/apps/basics/test/server.test.js +++ b/packages/kit/test/apps/basics/test/server.test.js @@ -452,7 +452,7 @@ test.describe('Errors', () => { expect(await response.text()).toMatch('thisvariableisnotdefined is not defined'); }); - test('custom error object', async ({ request }) => { + test('handleError hook can set the response status', async ({ request }) => { const response = await request.get('/errors/custom-error'); expect(response.status()).toBe(422); }); From 419662ebee4ad0d6c93969d09706ab214d9a739f Mon Sep 17 00:00:00 2001 From: Justin Baum Date: Thu, 13 Nov 2025 11:40:47 -0500 Subject: [PATCH 06/12] lint --- packages/kit/src/utils/error.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/packages/kit/src/utils/error.js b/packages/kit/src/utils/error.js index 5f4949a3104c..540f2089ae45 100644 --- a/packages/kit/src/utils/error.js +++ b/packages/kit/src/utils/error.js @@ -32,7 +32,12 @@ export function get_status(error) { return error.status; } // For overrides / custom error objects - if (typeof error === 'object' && error !== null && 'status' in error && typeof error.status === 'number') { + if ( + typeof error === 'object' && + error !== null && + 'status' in error && + typeof error.status === 'number' + ) { return error.status; } return 500; From dcea461c54095d164465f5bbf5f4818e78e80327 Mon Sep 17 00:00:00 2001 From: Justin Baum Date: Thu, 13 Nov 2025 18:54:09 -0500 Subject: [PATCH 07/12] new implementation --- packages/kit/src/exports/public.d.ts | 20 +++++++++++++++++++ packages/kit/src/runtime/server/page/index.js | 3 ++- .../runtime/server/page/respond_with_error.js | 10 ++++------ packages/kit/src/runtime/server/respond.js | 8 ++++++++ packages/kit/src/runtime/server/utils.js | 4 +++- packages/kit/src/types/internal.d.ts | 2 ++ .../kit/test/apps/basics/src/hooks.server.js | 4 ++++ .../src/routes/errors/custom-error/+server.js | 12 ++++------- packages/kit/types/index.d.ts | 20 +++++++++++++++++++ 9 files changed, 67 insertions(+), 16 deletions(-) diff --git a/packages/kit/src/exports/public.d.ts b/packages/kit/src/exports/public.d.ts index 3516689dce67..f4a3a6e412ab 100644 --- a/packages/kit/src/exports/public.d.ts +++ b/packages/kit/src/exports/public.d.ts @@ -1512,6 +1512,26 @@ export interface RequestEvent< * You cannot add a `set-cookie` header with `setHeaders` — use the [`cookies`](https://svelte.dev/docs/kit/@sveltejs-kit#Cookies) API instead. */ setHeaders: (headers: Record) => void; + /** + * Override the status code for error responses. This is useful when you want to customize the HTTP status code returned for an error, for example: + * + * ```js + * /// file: src/hooks.server.js + * export async function handleError({ error, event, status, message }) { + * // Return 503 Service Unavailable for database errors + * if (error.message.includes('database')) { + * event.setStatusCode(503); + * } + * + * return { + * message: 'An error occurred' + * }; + * } + * ``` + * + * This method can only be called from the `handleError` hook and only affects error responses. + */ + setStatusCode: (code: number) => void; /** * The requested URL. */ diff --git a/packages/kit/src/runtime/server/page/index.js b/packages/kit/src/runtime/server/page/index.js index 15d315905ab6..0eaf8b580cad 100644 --- a/packages/kit/src/runtime/server/page/index.js +++ b/packages/kit/src/runtime/server/page/index.js @@ -270,7 +270,8 @@ export async function render_page( return redirect_response(err.status, err.location); } - const status = get_status(err); + // Use custom status code if set via event.setStatusCode() + const status = event_state.error_status_code ?? get_status(err); const error = await handle_error_and_jsonify(event, event_state, options, err); while (i--) { diff --git a/packages/kit/src/runtime/server/page/respond_with_error.js b/packages/kit/src/runtime/server/page/respond_with_error.js index 0767e177d124..fcc37cc0712b 100644 --- a/packages/kit/src/runtime/server/page/respond_with_error.js +++ b/packages/kit/src/runtime/server/page/respond_with_error.js @@ -98,7 +98,7 @@ export async function respond_with_error({ ssr, csr }, - status, + status: event_state.error_status_code ?? status, error: await handle_error_and_jsonify(event, event_state, options, error), branch, fetched, @@ -114,10 +114,8 @@ export async function respond_with_error({ return redirect_response(e.status, e.location); } - return static_error_page( - options, - get_status(e), - (await handle_error_and_jsonify(event, event_state, options, e)).message - ); + const status = get_status(e); + const error = await handle_error_and_jsonify(event, event_state, options, e); + return static_error_page(options, event_state.error_status_code ?? status, error.message); } } diff --git a/packages/kit/src/runtime/server/respond.js b/packages/kit/src/runtime/server/respond.js index 9067418bc450..d30e99fe6228 100644 --- a/packages/kit/src/runtime/server/respond.js +++ b/packages/kit/src/runtime/server/respond.js @@ -193,6 +193,14 @@ export async function internal_respond(request, options, manifest, state) { } } }, + setStatusCode: (code) => { + if (typeof code !== 'number' || code < 100 || code > 599) { + throw new Error( + `Invalid status code: ${code}. Status code must be a number between 100 and 599` + ); + } + event_state.error_status_code = code; + }, url, isDataRequest: is_data_request, isSubRequest: state.depth > 0, diff --git a/packages/kit/src/runtime/server/utils.js b/packages/kit/src/runtime/server/utils.js index 3394bb038070..b790f4fc540b 100644 --- a/packages/kit/src/runtime/server/utils.js +++ b/packages/kit/src/runtime/server/utils.js @@ -84,9 +84,11 @@ export function static_error_page(options, status, message) { */ export async function handle_fatal_error(event, state, options, error) { error = error instanceof HttpError ? error : coalesce_to_error(error); - const status = get_status(error); const body = await handle_error_and_jsonify(event, state, options, error); + // Use custom status code if set via event.setStatusCode() + const status = state.error_status_code ?? get_status(error); + // ideally we'd use sec-fetch-dest instead, but Safari — quelle surprise — doesn't support it const type = negotiate(event.request.headers.get('accept') || 'text/html', [ 'application/json', diff --git a/packages/kit/src/types/internal.d.ts b/packages/kit/src/types/internal.d.ts index 84242df90417..1fff474a619a 100644 --- a/packages/kit/src/types/internal.d.ts +++ b/packages/kit/src/types/internal.d.ts @@ -611,6 +611,8 @@ export interface RequestState { remote_data?: Map>>; refreshes?: Record>; is_endpoint_request?: boolean; + /** Custom status code set by event.setStatusCode() */ + error_status_code?: number; } export interface RequestStore { diff --git a/packages/kit/test/apps/basics/src/hooks.server.js b/packages/kit/test/apps/basics/src/hooks.server.js index 34d0e9cba874..9f833aa5d88e 100644 --- a/packages/kit/test/apps/basics/src/hooks.server.js +++ b/packages/kit/test/apps/basics/src/hooks.server.js @@ -57,6 +57,10 @@ export const handleError = ({ event, error: e, status, message }) => { message = ev.locals.message; } + if (event.url.pathname.startsWith('/errors/custom-error')) { + event.setStatusCode(422); + } + return event.url.pathname.endsWith('404-fallback') ? undefined : { message: `${error.message} (${status} ${message})` }; diff --git a/packages/kit/test/apps/basics/src/routes/errors/custom-error/+server.js b/packages/kit/test/apps/basics/src/routes/errors/custom-error/+server.js index 6f9f0422d251..b1ef7274e044 100644 --- a/packages/kit/test/apps/basics/src/routes/errors/custom-error/+server.js +++ b/packages/kit/test/apps/basics/src/routes/errors/custom-error/+server.js @@ -1,10 +1,6 @@ -class CustomError extends Error { - constructor(message, errorOpts) { - super(message, errorOpts); - this.status = 422; - } -} - +/** + * This gets intercepted by the handleError hook and sets the status code to 422 + */ export function GET() { - throw new CustomError('Custom error'); + throw new Error('Custom error'); } diff --git a/packages/kit/types/index.d.ts b/packages/kit/types/index.d.ts index 0ddf7ca08844..940b9b16f694 100644 --- a/packages/kit/types/index.d.ts +++ b/packages/kit/types/index.d.ts @@ -1488,6 +1488,26 @@ declare module '@sveltejs/kit' { * You cannot add a `set-cookie` header with `setHeaders` — use the [`cookies`](https://svelte.dev/docs/kit/@sveltejs-kit#Cookies) API instead. */ setHeaders: (headers: Record) => void; + /** + * Override the status code for error responses. This is useful when you want to customize the HTTP status code returned for an error, for example: + * + * ```js + * /// file: src/hooks.server.js + * export async function handleError({ error, event, status, message }) { + * // Return 503 Service Unavailable for database errors + * if (error.message.includes('database')) { + * event.setStatusCode(503); + * } + * + * return { + * message: 'An error occurred' + * }; + * } + * ``` + * + * This method can only be called from the `handleError` hook and only affects error responses. + */ + setStatusCode: (code: number) => void; /** * The requested URL. */ From 70d8532ac646e33d70789cdef6f70ac492168fb2 Mon Sep 17 00:00:00 2001 From: Justin Baum Date: Thu, 13 Nov 2025 19:19:35 -0500 Subject: [PATCH 08/12] remove unneccessary --- packages/kit/src/utils/error.js | 9 --------- 1 file changed, 9 deletions(-) diff --git a/packages/kit/src/utils/error.js b/packages/kit/src/utils/error.js index 540f2089ae45..7de1357e11e7 100644 --- a/packages/kit/src/utils/error.js +++ b/packages/kit/src/utils/error.js @@ -31,15 +31,6 @@ export function get_status(error) { if (error instanceof HttpError || error instanceof SvelteKitError) { return error.status; } - // For overrides / custom error objects - if ( - typeof error === 'object' && - error !== null && - 'status' in error && - typeof error.status === 'number' - ) { - return error.status; - } return 500; } From 78deb46d583cac5ae14f5e18e6195f2742340b3f Mon Sep 17 00:00:00 2001 From: Justin Baum Date: Thu, 13 Nov 2025 20:02:43 -0500 Subject: [PATCH 09/12] catch all places where status code is set --- packages/kit/src/exports/public.d.ts | 2 +- packages/kit/src/runtime/server/data/index.js | 20 +++++++++---- .../kit/src/runtime/server/page/actions.js | 28 +++++++++++++------ .../runtime/server/page/respond_with_error.js | 4 +-- packages/kit/src/runtime/server/remote.js | 18 ++++++++---- 5 files changed, 49 insertions(+), 23 deletions(-) diff --git a/packages/kit/src/exports/public.d.ts b/packages/kit/src/exports/public.d.ts index f4a3a6e412ab..d91ea4f94c64 100644 --- a/packages/kit/src/exports/public.d.ts +++ b/packages/kit/src/exports/public.d.ts @@ -1529,7 +1529,7 @@ export interface RequestEvent< * } * ``` * - * This method can only be called from the `handleError` hook and only affects error responses. + * This method should only be called from the `handleError` hook and will only affect error responses. */ setStatusCode: (code: number) => void; /** diff --git a/packages/kit/src/runtime/server/data/index.js b/packages/kit/src/runtime/server/data/index.js index 83e16c3a9d12..e127acacb8a0 100644 --- a/packages/kit/src/runtime/server/data/index.js +++ b/packages/kit/src/runtime/server/data/index.js @@ -107,13 +107,18 @@ export async function render_data( // Math.min because array isn't guaranteed to resolve in order length = Math.min(length, i + 1); + const error_body = await handle_error_and_jsonify(event, event_state, options, error); + // Use custom status code if set via event.setStatusCode() + const status = + event_state.error_status_code ?? + (error instanceof HttpError || error instanceof SvelteKitError + ? error.status + : undefined); + return /** @type {import('types').ServerErrorNode} */ ({ type: 'error', - error: await handle_error_and_jsonify(event, event_state, options, error), - status: - error instanceof HttpError || error instanceof SvelteKitError - ? error.status - : undefined + error: error_body, + status }); }) ) @@ -156,7 +161,10 @@ export async function render_data( if (error instanceof Redirect) { return redirect_json_response(error); } else { - return json_response(await handle_error_and_jsonify(event, event_state, options, error), 500); + const error_body = await handle_error_and_jsonify(event, event_state, options, error); + // Use custom status code if set via event.setStatusCode() + const status = event_state.error_status_code ?? 500; + return json_response(error_body, status); } } } diff --git a/packages/kit/src/runtime/server/page/actions.js b/packages/kit/src/runtime/server/page/actions.js index 0d4838cc8c61..35c96d4ab6d5 100644 --- a/packages/kit/src/runtime/server/page/actions.js +++ b/packages/kit/src/runtime/server/page/actions.js @@ -36,13 +36,18 @@ export async function handle_action_json_request(event, event_state, options, se `POST method not allowed. No form actions exist for ${DEV ? `the page at ${event.route.id}` : 'this page'}` ); + const error = await handle_error_and_jsonify(event, event_state, options, no_actions_error); + + // Use custom status code if set via event.setStatusCode() + const status = event_state.error_status_code ?? no_actions_error.status; + return action_json( { type: 'error', - error: await handle_error_and_jsonify(event, event_state, options, no_actions_error) + error }, { - status: no_actions_error.status, + status, headers: { // https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/405 // "The server must generate an Allow header field in a 405 status code response" @@ -93,18 +98,23 @@ export async function handle_action_json_request(event, event_state, options, se return action_json_redirect(err); } + const error = await handle_error_and_jsonify( + event, + event_state, + options, + check_incorrect_fail_use(err) + ); + + // Use custom status code if set via event.setStatusCode() + const status = event_state.error_status_code ?? get_status(err); + return action_json( { type: 'error', - error: await handle_error_and_jsonify( - event, - event_state, - options, - check_incorrect_fail_use(err) - ) + error }, { - status: get_status(err) + status } ); } diff --git a/packages/kit/src/runtime/server/page/respond_with_error.js b/packages/kit/src/runtime/server/page/respond_with_error.js index fcc37cc0712b..4a249a002a3d 100644 --- a/packages/kit/src/runtime/server/page/respond_with_error.js +++ b/packages/kit/src/runtime/server/page/respond_with_error.js @@ -114,8 +114,8 @@ export async function respond_with_error({ return redirect_response(e.status, e.location); } - const status = get_status(e); + const status = event_state.error_status_code ?? get_status(e); const error = await handle_error_and_jsonify(event, event_state, options, e); - return static_error_page(options, event_state.error_status_code ?? status, error.message); + return static_error_page(options, status, error.message); } } diff --git a/packages/kit/src/runtime/server/remote.js b/packages/kit/src/runtime/server/remote.js index 88ae0d41387c..4b8dab331380 100644 --- a/packages/kit/src/runtime/server/remote.js +++ b/packages/kit/src/runtime/server/remote.js @@ -79,11 +79,15 @@ async function handle_remote_call_internal(event, state, options, manifest, id) try { return { type: 'result', data: get_result(arg, i) }; } catch (error) { + const error_body = await handle_error_and_jsonify(event, state, options, error); + // Use custom status code if set via event.setStatusCode() + const status = + state.error_status_code ?? + (error instanceof HttpError || error instanceof SvelteKitError ? error.status : 500); return { type: 'error', - error: await handle_error_and_jsonify(event, state, options, error), - status: - error instanceof HttpError || error instanceof SvelteKitError ? error.status : 500 + error: error_body, + status }; } }) @@ -183,13 +187,17 @@ async function handle_remote_call_internal(event, state, options, manifest, id) ); } + const error_body = await handle_error_and_jsonify(event, state, options, error); + + // Use custom status code if set via event.setStatusCode() const status = - error instanceof HttpError || error instanceof SvelteKitError ? error.status : 500; + state.error_status_code ?? + (error instanceof HttpError || error instanceof SvelteKitError ? error.status : 500); return json( /** @type {RemoteFunctionResponse} */ ({ type: 'error', - error: await handle_error_and_jsonify(event, state, options, error), + error: error_body, status }), { From 4a956e933e75f9749d4e524ec6d434c291f4a2ee Mon Sep 17 00:00:00 2001 From: Justin Baum Date: Thu, 13 Nov 2025 20:10:23 -0500 Subject: [PATCH 10/12] update types --- packages/kit/types/index.d.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/kit/types/index.d.ts b/packages/kit/types/index.d.ts index 940b9b16f694..4ceae54dd10a 100644 --- a/packages/kit/types/index.d.ts +++ b/packages/kit/types/index.d.ts @@ -1505,7 +1505,7 @@ declare module '@sveltejs/kit' { * } * ``` * - * This method can only be called from the `handleError` hook and only affects error responses. + * This method should only be called from the `handleError` hook and will only affect error responses. */ setStatusCode: (code: number) => void; /** From 64d2917a98c8efa2f73b7715031ee99e97b3f2f7 Mon Sep 17 00:00:00 2001 From: Justin Baum Date: Thu, 13 Nov 2025 20:14:56 -0500 Subject: [PATCH 11/12] checkout error.js --- packages/kit/src/utils/error.js | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/packages/kit/src/utils/error.js b/packages/kit/src/utils/error.js index 7de1357e11e7..c12b98eff41c 100644 --- a/packages/kit/src/utils/error.js +++ b/packages/kit/src/utils/error.js @@ -25,13 +25,9 @@ export function normalize_error(error) { /** * @param {unknown} error - * @returns {number} */ export function get_status(error) { - if (error instanceof HttpError || error instanceof SvelteKitError) { - return error.status; - } - return 500; + return error instanceof HttpError || error instanceof SvelteKitError ? error.status : 500; } /** From 9ca1978944198612552cdace72cfda97ffc5f0e3 Mon Sep 17 00:00:00 2001 From: Justin Baum Date: Thu, 13 Nov 2025 20:17:35 -0500 Subject: [PATCH 12/12] improve styling --- packages/kit/src/runtime/server/page/respond_with_error.js | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/packages/kit/src/runtime/server/page/respond_with_error.js b/packages/kit/src/runtime/server/page/respond_with_error.js index 4a249a002a3d..e64e75c52cae 100644 --- a/packages/kit/src/runtime/server/page/respond_with_error.js +++ b/packages/kit/src/runtime/server/page/respond_with_error.js @@ -90,6 +90,9 @@ export async function respond_with_error({ ); } + const error_body = await handle_error_and_jsonify(event, event_state, options, error); + const status = event_state.error_status_code ?? get_status(error); + return await render_response({ options, manifest, @@ -98,8 +101,8 @@ export async function respond_with_error({ ssr, csr }, - status: event_state.error_status_code ?? status, - error: await handle_error_and_jsonify(event, event_state, options, error), + status, + error: error_body, branch, fetched, event,