#Returning handled/expected errors back to the client
9 messages · Page 1 of 1 (latest)
Hi @white laurel. I'm kapa.ai, your friendly support bot. I will try to answer your question by searching through the knowledge base. :wave:
The best way to send handled or expected errors (such as incorrect input or usage limits) back to the client in Convex is to throw a ConvexError from your function. This approach is idiomatic in Convex for "application errors"—cases where you anticipate certain failure modes, like invalid user input or business logic constraints.
Here's how you can do it:
- Throw a ConvexError with a message or structured data when you encounter an expected error condition in your function.
- On the client, catch the error and check if it is an instance of ConvexError. You can then access the error's data property to display a user-friendly message or handle the error programmatically.
Example (server-side):
import { ConvexError } from "convex/values";
import { mutation } from "./_generated/server";
export const assignRole = mutation({
args: { /* ... */ },
handler: (ctx, args) => {
const isTaken = isRoleTaken(/* ... */);
if (isTaken) {
throw new ConvexError("Role is already taken");
}
// ...
},
});
Example (client-side):
import { ConvexError } from "convex/values";
import { useMutation } from "convex/react";
import { api } from "../convex/_generated/api";
export function MyApp() {
const doSomething = useMutation(api.myFunctions.mutateSomething);
const handleSomething = async () => {
try {
await doSomething({ a: 1, b: 2 });
} catch (error) {
const errorMessage =
error instanceof ConvexError
? (error.data as { message: string }).message
: "Unexpected error occurred";
// Handle or display errorMessage
}
};
// ...
}
Throwing ConvexError is also important for mutations, as it ensures that the transaction is rolled back and no partial state is committed if an error occurs. You can also pass structured data to ConvexError for more granular error handling on the client side Functions > Error-handling: Application Errors.
This pattern is recommended over returning error objects, especially in mutations, because returning instead of throwing would not trigger a rollback and could lead to inconsistent state Functions > Error-handling: Application Errors.
In summary:
Yes, you should use throw ConvexError for expected errors to send them back to the client in a structured and idiomatic way.
Note: you can ask me a follow up question by @ mentioning me again :speech_balloon:
@white laurel Kapa.ai is still learning and improving, please let me know how I did by reacting below.
@keen heath the problem is doing throw Convex error shows up as uncaught - and counts a "functions" errors and sends to Sentry.
[CONVEX M(public/job:createJob)] Uncaught ConvexError: Please upgrade to have more trackers
Let me check the knowledge base for you again. One moment! :books:
You're correct that when you throw a ConvexError in a function, it is treated as an uncaught error by Convex, and this will be logged as a function error and reported to Sentry or other exception tracking services if you have them configured. This is the current behavior in Convex: both application errors (like ConvexError) and unexpected errors are surfaced in logs and exception reporting integrations Exception Reporting.
This means that even expected, user-facing errors (such as usage limits or invalid input) will show up in your logs and Sentry as "function errors." There is currently no built-in way to distinguish between "expected" ConvexError errors and true internal/server errors in the reporting pipeline—they are all reported as errors.
This has been discussed in the community, and some users have expressed a desire for a way to separate or suppress reporting of expected application errors (like ConvexError) from true server errors, but as of now, this is not supported out of the box Discord: Query/Mutation Error Handling & Transactional Integrity Advice, Discord: Need help with handling Convex Auth signIn errors in prod.