#is there a way to use convex values argument schema to also validate the form on the frontend usingr
18 messages · Page 1 of 1 (latest)
Hi @sullen axle. I'm kapa.ai, your friendly support bot. I will try to answer your question by searching through the knowledge base. :wave:
Convex's argument validation uses its own validator system (v from convex/values) for server-side validation and type inference. This system is designed to ensure that arguments passed to Convex functions match the expected types at runtime, and it integrates tightly with your backend and TypeScript types. However, the knowledge sources do not mention any built-in way to directly use Convex's v validators for client-side form validation in React, including with libraries like react-hook-form.
The recommended approach for keeping validation logic in sync between frontend and backend is to define your validators (e.g., for fields or argument shapes) in a shared location (like your schema file), and then import them for use in both your Convex functions and, if possible, your frontend. However, Convex's v validators are not designed to run in the browser—they are intended for server-side use only, and there is no documented method to use them directly with react-hook-form on the frontend.
If you want to share validation logic between frontend and backend, the knowledge sources suggest using a library like Zod, which can be used both in the browser and on the server. You can define your schemas with Zod, use them for client-side validation (including with react-hook-form), and then use helpers like convex-helpers/server/zod to convert Zod schemas into Convex argument validators for your backend functions. This allows you to avoid duplicating validation logic and keep your types in sync across your stack. Here’s an example of using Zod with Convex:
import { z } from "zod";
import { NoOp } from "convex-helpers/server/customFunctions";
import { zCustomQuery } from "convex-helpers/server/zod";
import { query } from "./_generated/server";
// Make this once, to use anywhere you would have used query
const zQuery = zCustomQuery(query, NoOp);
export const greeting = zQuery({
args: { name: z.string() },
handler: async (_ctx, args) => {
return Hello ${args.name};
},
});
You can then use the same Zod schema for your react-hook-form validation on the frontend, ensuring consistency between client and server validation logic [Zod with TypeScript for Server-side Validation and End-to-End Types](<https://stack.convex.dev/typescript-zod-function-validation#using-zod-for-argument-validation-server-side>).
**Summary:**
- Convex's `v` validators are not designed for frontend use and cannot be used directly with react-hook-form.
- For shared validation, use Zod schemas, which can be used both on the frontend (with react-hook-form) and backend (with Convex, via helpers) [Zod with TypeScript for Server-side Validation and End-to-End Types](<https://stack.convex.dev/typescript-zod-function-validation#using-zod-for-argument-validation-server-side>).
----------------------
Note: you can ask me a follow up question by @ mentioning me again :speech_balloon:
----------------------
@sullen axle Kapa.ai is still learning and improving, please let me know how I did by reacting below.
@latent mauve how would i create a unique field in a document in schema DB
Let me check the knowledge base for you again. One moment! :books:
To create a unique field in a Convex document, you need to enforce uniqueness in your application logic—Convex does not currently support a built-in "unique" constraint at the schema/database level.
The recommended pattern is:
- Create an index on the field you want to be unique.
- Before inserting or updating a document, query for existing documents with that field value.
- If a document exists, throw an error or update it (upsert pattern); otherwise, insert the new document.
Example:
// schema.ts
users: defineTable({
email: v.string(),
}).index("email", ["email"]);
// myFunctions.ts
const existing = await ctx.db
.query("users")
.withIndex("email", (q) => q.eq("email", email))
.first();
if (existing !== null) {
throw new Error(
`In table "users" cannot create a duplicate document with field "email" of value \`${email}\`, existing document with ID "${existing._id}" already has it.`,
);
}
// Otherwise, safe to insert
await ctx.db.insert("users", { email });
This ensures that no two documents have the same value for the email field, as long as all inserts go through this logic. The use of transactions in Convex mutations ensures this is safe from race conditions Unique field values.
Note:
- If you use Convex Ents, you can declare a field as unique and the check is performed automatically.
- There is no schema-level or DB-level unique constraint; it must be enforced in code Creating unique fields with upserts.
If you need more advanced patterns (like upserts), see the upsert example.
Note: you can ask me a follow up question by @ mentioning me again :speech_balloon:
@sullen axle Kapa.ai is still learning and improving, please let me know how I did by reacting below.
@latent mauve how would i show the client the error message not a full stack trace
Let me check the knowledge base for you again. One moment! :books:
To show the client a specific error message (not a full stack trace) when something goes wrong in a Convex function, you should throw a ConvexError with a custom message or data payload. On the client, you can catch this error and display the message to the user.
How to do it:
1. Throw a ConvexError in your backend function:
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");
}
// ...
},
});
2. Catch and display the error on the client:
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";
// Show errorMessage to the user
}
};
// ...
}
In production:
- Only the custom
datayou pass to ConvexError will be sent to the client, not the stack trace or internal details. This keeps your error messages user-friendly and secure. - Developer errors or unexpected errors will show a generic "Server Error" message in production, not a stack trace or sensitive details.
See: Application Errors, Differences in error reporting between dev and prod.
Summary:
- Use
ConvexErrorfor user-facing error messages. - Catch and display the error message on the client.
- Stack traces and sensitive info are never sent to the client in production.
Note: you can ask me a follow up question by @ mentioning me again :speech_balloon:
@sullen axle Kapa.ai is still learning and improving, please let me know how I did by reacting below.
@latent mauve ConvexError: [CONVEX Q(notifications/workers/email:getNotifications)] [Request ID: b1ef060f7b08235b] Server Error
Called by client
Let me check the knowledge base for you again. One moment! :books:
The error message you are seeing:
ConvexError: [CONVEX Q(notifications/workers/email:getNotifications)] [Request ID: b1ef060f7b08235b] Server Error
Called by client
indicates that a server error occurred when calling the getNotifications query from the client. In production, Convex intentionally redacts detailed error messages and stack traces for server errors, only exposing the function name and a generic "Server Error" message to the client. This is a security feature to prevent leaking sensitive information. The full error details, including stack traces, are available in your Convex deployment's logs for debugging purposes Differences in error reporting between dev and prod.
Key points:
- In production, only the function name and a generic message are sent to the client for server errors.
- Application errors (thrown with
ConvexError) will include their customdatapayload on the client. - For debugging, check the Logs tab in the Convex dashboard using the provided Request ID.