conformZodMessage
A set of custom messages to control the validation behavior. This is useful if you need async validation for one of the fields.
#Options
conformZodMessage.VALIDATION_SKIPPED
This message is used to indicate that the validation is skipped and Conform should use the previous result instead.
conformZodMessage.VALIDATION_UNDEFINED
This message is used to indicate that the validation is not defined and Conform should fallback to server validation.
#Example
Here is a signup form example which validates if the email is unique.
1import type { Intent } from '@conform-to/react';
2import { useForm } from '@conform-to/react';
3import { parseWithZod, conformZodMessage } from '@conform-to/zod';
4// If you are using Zod v4, update the imports:
5// import { parseWithZod, conformZodMessage } from '@conform-to/zod/v4';
6import { z } from 'zod';
7
8// Instead of sharing a schema, prepare a schema creator
9function createSchema(
10 // The `intent` will be provided by the `parseWithZod` helper
11 intent: Intent | null,
12 options?: {
13 isEmailUnique: (email: string) => Promise<boolean>;
14 },
15) {
16 return z
17 .object({
18 email: z
19 .string()
20 .email()
21 // Pipe the schema so it runs only if the email is valid
22 .pipe(
23 z.string().superRefine((email, ctx) => {
24 const isValidatingEmail =
25 intent === null ||
26 (intent.type === 'validate' && intent.payload.name === 'email');
27
28 if (!isValidatingEmail) {
29 ctx.addIssue({
30 code: 'custom',
31 message: conformZodMessage.VALIDATION_SKIPPED,
32 });
33 return;
34 }
35
36 if (typeof options?.isEmailUnique !== 'function') {
37 ctx.addIssue({
38 code: 'custom',
39 message: conformZodMessage.VALIDATION_UNDEFINED,
40 fatal: true,
41 });
42 return;
43 }
44
45 return options.isEmailUnique(email).then((isUnique) => {
46 if (!isUnique) {
47 ctx.addIssue({
48 code: 'custom',
49 message: 'Email is already used',
50 });
51 }
52 });
53 }),
54 ),
55 })
56 .and(
57 z
58 .object({
59 password: z.string({ required_error: 'Password is required' }),
60 confirmPassword: z.string({
61 required_error: 'Confirm password is required',
62 }),
63 })
64 .refine((data) => data.password === data.confirmPassword, {
65 message: 'Password does not match',
66 path: ['confirmPassword'],
67 }),
68 );
69}
70
71export async function action({ request }) {
72 const formData = await request.formData();
73 const submission = await parseWithZod(formData, {
74 schema: (intent) =>
75 // create the zod schema based on the intent
76 createSchema(intent, {
77 isEmailUnique(email) {
78 // query from your database
79 },
80 }),
81 async: true,
82 });
83
84 if (submission.status !== 'success') {
85 return submission.reply();
86 }
87
88 // ...
89}
90
91export default function Signup() {
92 const lastResult = useActionData();
93 const [form, fields] = useForm({
94 lastResult,
95 onValidate({ formData }) {
96 return parseWithZod(formData, {
97 // Create the schema without `isEmailUnique` defined
98 schema: (intent) => createSchema(intent),
99 });
100 },
101 });
102
103 // ...
104}