What is the suggested way for our application to throw expected application errors without having them be seen as exceptions? For example, if a user has invalid input or tries to log in with the wrong password, this isn't something that we want to be treating as an exception for our error reporting and logging tools. What's the best way to do this? Because right now even a failed login is being seen as a 500 exception.
#Applications errors
16 messages · Page 1 of 1 (latest)
Hi @ashen drift. I'm kapa.ai, your friendly support bot. I will try to answer your question by searching through the knowledge base. :wave:
You have two main options for “expected” failures like invalid input or wrong password, and both avoid treating them as unexpected 500s in your own reporting.
1. Return a typed “error” result (no throw)
Have the function return a union type that encodes success vs failure, and don’t throw at all:
// Example: login mutation
export const login = mutation({
args: { email: v.string(), password: v.string() },
handler: async (ctx, args) => {
const user = await getUserByEmail(ctx, args.email);
if (!user) {
return { ok: false as const, error: "USER_NOT_FOUND" as const };
}
if (!checkPassword(user, args.password)) {
return { ok: false as const, error: "INVALID_PASSWORD" as const };
}
// success
return { ok: true as const, userId: user._id };
},
});
This pattern is explicitly documented: for expected failures you can “return different values,” e.g.:
Id<"users"> | { error: "EMAIL_ADDRESS_IN_USE" };
so the client must handle both success and error branches in a type-safe way.[Application errors]
No exception is thrown, so nothing is reported as a 500 unless there’s a true system / developer error.
2. Throw ConvexError for expected failures
If you prefer using exceptions for control flow (e.g. to bubble from helpers, or to abort a mutation transaction), use ConvexError with structured data:
import { ConvexError } from "convex/values";
export const login = mutation({
args: { email: v.string(), password: v.string() },
handler: async (ctx, args) => {
const user = await getUserByEmail(ctx, args.email);
if (!user || !checkPassword(user, args.password)) {
throw new ConvexError({
code: "INVALID_CREDENTIALS",
message: "Email or password is incorrect",
});
}
// ...
},
});
On the client, you distinguish application vs “real” errors:
```ts
import { ConvexError } from "convex/values";
try {
await login({ email, password });
} catch (error) {
if (error instanceof ConvexError) {
const data = error.data as { code: string; message: string };
// handle expected error in UI, *don’t* send to exception tooling
showToast(data.message);
} else {
// developer/read-limit/internal error: send to Sentry, etc.
reportError(error);
}
}
Convex specifically recommends ConvexError for expected failures where you still want transactional rollback and structured data; these are called “application errors”.[Application errors] You can configure your error-reporting integration to only capture non‑ConvexError exceptions, or to treat ConvexError differently based on error.data.
Which should you use?
- If you don’t want these paths to show up as exceptions at all in your tooling: prefer option 1 (return typed results).
- If you like exception bubbling and automatic transaction aborts but want to avoid noisy “500s” in monitoring: use option 2 and filter
ConvexError(or specificcodes inerror.data) out of your “unexpected error” reporting.
Both approaches are explicitly supported; the docs note that “If you have expected ways your functions might fail, you can either return different values or throw ConvexErrors.”[App errors overview; Application errors]
Note: you can ask me a follow up question by @ mentioning me again :speech_balloon:
@ashen drift Kapa.ai is still learning and improving, please let me know how I did by reacting below.
@digital hornet So if I've hooked Sentry up to my backend and I throw a Convex error, will Sentry see that as an exception similar to the other types of raw exceptions that we might encounter?
And secondly, there's no way for us to customize the response code for a Convex mutation or query, right?
Let me check the knowledge base for you again. One moment! :books:
- ConvexError + Sentry
Convex’s exception reporting integration sends all function exceptions (query/mutation/action/httpAction) to Sentry; the docs and Discord threads don’t state any special-case filtering for ConvexError vs other Errors.[Exception reporting; Error handling]
So based on the available docs, you should assume that:
- Throwing a
ConvexErrorin a function will be treated as an exception and reported to Sentry just like other thrown errors. - If you want to avoid certain expected failures (like invalid credentials) being treated as “real exceptions” in Sentry, you either:
- Return typed values instead of throwing (no exception, nothing to report),[Application errors] or
- Catch the error on the client and only forward non‑
ConvexError(or certaincodes inerror.data) to Sentry using your own Sentry client SDK. The built‑in Convex↔Sentry integration itself is not documented as configurable at that level.[Exception reporting]
The docs also explicitly say the built-in Sentry support “does not yet support the advanced customization provided by the Sentry SDK,” which implies you can’t, for example, plug in custom beforeSend logic server-side through Convex’s integration alone.[Exception reporting]
- Custom HTTP status codes for queries/mutations
Convex queries and mutations are RPC-style functions; the public API doesn’t expose any way to set a custom HTTP status code for their responses. The error-handling docs talk about what happens when they throw (promise rejection, optimistic update rollback, etc.) but never mention configuring HTTP codes.[Errors in mutations; Error handling]
The only place you can directly control HTTP status codes is in httpAction, where you return a Response object yourself:
return new Response("no!", { status: 400, statusText: "Missing Secret" });
as shown in the custom wrapper example.[Custom httpAction]
So, from the available information:
- No, there’s no documented way to customize HTTP status codes for queries or mutations.
- If you need specific HTTP semantics (e.g. 401 vs 400), that needs to be done via
httpAction, not via standard Convex queries/mutations.
Note: you can ask me a follow up question by @ mentioning me again :speech_balloon:
@ashen drift Kapa.ai is still learning and improving, please let me know how I did by reacting below.
@ashen drift curious what you do to filter these as being "exceptions" for sentry
@compact harbor So far I've just been tanking the spam but eventually I need to redesign my endpoints so that they return error objects instead of throwing errors. I'm used to designing APIs that would throw custom errors and use HTTP status codes. That's why I originally built it to throw errors but I need to change the response type.
facing the same issue. on Sentry side, you can use advanced inbound filtering to filter out "non-error" exceptions, but it's only available on the $80/month plan, which is a little steep