Skip to content

breaking: allow handleError to influence status code#16162

Open
dummdidumm wants to merge 16 commits into
version-3from
handle-error-status
Open

breaking: allow handleError to influence status code#16162
dummdidumm wants to merge 16 commits into
version-3from
handle-error-status

Conversation

@dummdidumm

Copy link
Copy Markdown
Member

...by returning { status: ..., ... } from handleError.

Breaking change because theoretically someone could've used this as part of App.Error before, and now it's kind of outside that, a reserved property on handleError's return object.

Closes #14442

...by returning `{ status: ..., ... }` from `handleError`.

Breaking change because theoretically someone could've used this as part of `App.Error` before, and now it's kind of outside that, a reserved property on `handleError`'s return object.

Closes #14442
@changeset-bot

changeset-bot Bot commented Jun 24, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 16f552d

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
@sveltejs/kit Major

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@svelte-docs-bot

Copy link
Copy Markdown

Comment thread documentation/docs/30-advanced/25-errors.md Outdated
Co-authored-by: vercel[bot] <35613825+vercel[bot]@users.noreply.github.com>
@dummdidumm

Copy link
Copy Markdown
Member Author

/autofix

Comment thread packages/kit/test/apps/basics/test/cross-platform/test.js Outdated
Co-authored-by: Tee Ming <chewteeming01@gmail.com>
Comment thread packages/kit/src/exports/public.d.ts Outdated
status: number;
message: string;
}) => MaybePromise<void | App.Error>;
}) => MaybePromise<void | (App.Error & { status?: number })>;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what if instead of doing it like this, App.Error always had a status property? we would update the error(...) helper like so:

// just a status, default message, works as today
error(418);

// status and message, works as today
error(418, `I'm a teapot`);

// pass an `App.Error` object — unlike today, omit the first argument
error({
  id: crypto.randomUUID(),
  status: 418,
  message: `I'm a teapot`
});

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I suppose another option for the custom App.Error case would be this, if it's possible type-wise

error(418, `I'm a teapot`, {
  id: crypto.randomUUID()
});

@teemingc teemingc Jun 26, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agree on adding the status property to our interface. Prevents people from overriding it in app.d.ts with a different type (ours takes precedence and causes the type in app.d.ts to be ignored). Not sure how to get it to error at the interface so that folks know it doesn't take effect. I guess that becomes obvious if they do try to assign a value to it.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if it's possible type-wise

just took a run at this

type Custom = App.Error extends { status: number; message: string }
    ? { status: number; message: string } extends App.Error
        ? false
        : true
    : never;

declare function error(
    ...args: Custom extends true
        ? [status: number, message: string, body: Omit<App.Error, 'status' | 'message'>]
        : [status: number, message: string]
): never;

@Rich-Harris Rich-Harris left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

blocking pending resolution of #16162 (review)

@Rich-Harris Rich-Harris added the needs-decision Not sure if we want to do this yet, also design work needed label Jun 26, 2026
@Rich-Harris Rich-Harris added this to the 3.0 milestone Jun 26, 2026

@vercel vercel Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Additional Suggestion:

Client-side handle_error returns an App.Error without a status field, so after CSR navigation errors page.error.status is undefined even though App.Error.status is now a required number.

Fix on Vercel

Comment thread packages/kit/src/exports/public.d.ts Outdated
vercel Bot and others added 2 commits June 29, 2026 19:53
…rapper, so async `handleValidationError` hooks no longer type-check even though the runtime awaits the result.

This commit fixes the issue reported at packages/kit/src/exports/public.d.ts:972

## Bug

In commit `60cd975`, the `HandleValidationError` type in `packages/kit/src/exports/public.d.ts` was changed:

```ts
- (input: { issues: Issue[]; event: RequestEvent }) => MaybePromise<App.Error>;
+ (input: { issues: Issue[]; event: RequestEvent }) => AppErrorWithOptionalStatus;
```

The switch from `App.Error` to `AppErrorWithOptionalStatus` is intentional (status is now optional — the runtime does `error(body.status ?? 400, body)`). But dropping the `MaybePromise` wrapper is a regression.

### Why it's a bug / failure mode

The runtime explicitly awaits the hook result in `packages/kit/src/runtime/app/server/remote/shared.js`:

```js
const body = await state.handleValidationError({ issues: result.issues, event });
error(body.status ?? 400, body);
```

So returning a `Promise` works at runtime. With the type no longer permitting a `Promise`, a user with an async hook gets a TypeScript error:

```ts
export async function handleValidationError({ issues }) {
  return { message: await translate(issues[0].message) };
}
```

This is inconsistent with the sibling hooks `HandleServerError` / `HandleClientError`, which use `MaybePromise<void | AppErrorWithOptionalStatus>`, and the type had been `MaybePromise<App.Error>` since its introduction.

## Fix

Restore the `MaybePromise` wrapper:

```ts
(input: { issues: Issue[]; event: RequestEvent }) => MaybePromise<AppErrorWithOptionalStatus>;
```

Applied to both `packages/kit/src/exports/public.d.ts` (line 972) and the generated `packages/kit/types/index.d.ts` (line 945) to keep them in sync. `MaybePromise` is already imported/in scope in both files (used by the sibling hooks).

Co-authored-by: Vercel <vercel[bot]@users.noreply.github.com>
Co-authored-by: dummdidumm <sholthausen@web.de>
@dummdidumm dummdidumm dismissed Rich-Harris’s stale review June 29, 2026 20:10

App.Error now has status property

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

breaking change needs-decision Not sure if we want to do this yet, also design work needed

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Allow intercepting errors from loaders

3 participants