typescriptzod

Issues with Zod Schema Refinements and Conditional Logic in TypeScript


I'm working on a Zod validation schema for a product name input field that needs to be flexible in handling different validation requirements, such as minimum/maximum length and allowing special characters.

Here's a simplified version of the schema:

  const { minLength = 3, maxLength = 50, allowSpecialCharacters = false, required = true } = options || {};

  let schema = z.string({ required_error: `${displayName} is required` });

  if (required) {
    schema = schema.min(minLength, { message: `${displayName} must be at least ${minLength} characters long` });
  }

  schema = schema.max(maxLength, { message: `${displayName} must be less than ${maxLength} characters` });

  if (!allowSpecialCharacters) {
    schema = schema.refine((value) => /^[a-zA-Z0-9\s]*$/.test(value), {
      message: `${displayName} must not contain special characters`
    });
  }

  return schema.trim();
}

However, I'm encountering a TypeScript error when trying to reassign schema in the conditional logic:

Type 'ZodEffects<ZodString, string, string>' is missing the following properties from type 'ZodString': _regex, _addCheck, email, url, and 39 more.ts(2740)
let schema: z.ZodString

It seems like after applying refine, Zod returns a ZodEffects type, which leads to a type mismatch. How can I conditionally chain these refinements while maintaining type compatibility? Any insights would be appreciated!


Solution

  • Your running into this issue because, as you've seen, .refine() changes the schema type to ZodEffects. One thing to note about ZodEffects is that it's a general schema type and doesn't have type specific methods like .min(), .max(), .email(), etc. So even if we, for example, define schema: ZodString | ZodEffects<string,..>, you'll get type issues trying to call the other utility methods. Here's an issue detailing the returned ZodEffects, and the maintainer says in the thread that this will be changed in Zod 4.

    But you're in luck. ZodString has the method you need without using .refine(), it's .regex(), which will still return a ZodString. So just change your special character check to this:

    // ....
      if (!allowSpecialCharacters) {
        schema = schema.regex(/^[a-zA-Z0-9\s]*$/, {
          message: `${displayName} must not contain special characters`
        });
      }