zod
zod copied to clipboard
Unable to Chain .min() and Other Validation Methods After .refine() on z.string()
Zod version: 3.23.8
When writing the following Zod schema:
z.string()
.refine((val) => val === "John", {
message: "String must be equal to 'John'",
})
.min(1)
I expected this validation code to be valid and check whether the string has a minimum length of 1. However, instead of that, I get an error stating that .min() is not a function. This error suggests that chaining .min() or other validation methods after .refine() is not supported.
Expected Behavior:
I expect to still be able to use .min() and other validation methods (such as .max(), etc.) after using .refine(). This would allow me to build more complex validation rules without running into method chaining issues.
Current Behavior:
Currently, attempting to chain .min() (or similar validation methods) after .refine() on z.string() results in an error: TypeError: .min is not a function. This behavior limits the flexibility of combining custom validation logic with built-in methods.
Use Case:
In my codebase, I rely on the ability to combine custom validation logic using .refine() with built-in methods such as .min(). Being able to chain these methods makes the code more concise and maintainable.
Proposed Solution:
It would be helpful if .min(), .max(), and similar validation methods were still accessible after using .refine(), or if there were an alternative approach to achieve the same result.
Request for Feedback:
I would appreciate feedback on whether this behavior is intentional, and if so, whether there are recommended workarounds or plans to support this kind of chaining in the future.
I've come up with a temporary workaround using an if statement to check whether the .min() method is available:
import z from "zod";
let schema = z.string();
schema = schema.refine((val) => val === "John", {
message: "String must be equal to 'John'",
});
if (schema.min) {
console.log("min is available to use");
schema = schema.min(1);
} else {
console.log("min is NOT available to use");
schema = schema.refine((val) => val.length >= 1);
}
console.log(schema.safeParse("John"));
While this approach works, it adds unnecessary complexity and feels a bit messy. It would be much more straightforward if chaining methods like .min() after .refine() were supported natively in Zod.
After you use something like refine() or transform(), the return value will be a ZodEffect, not a ZodType anymore. While you can use pipe() to chain another schema, it can seem verbose in this scenario.
z
.string()
.refine((val) => val === "John", {
message: "String must be equal to 'John'",
})
.pipe(z.string().min(10));
Hi, @RalkeyOfficial. I'm Dosu, and I'm helping the Zod team manage their backlog. I'm marking this issue as stale.
Issue Summary:
- You reported a
TypeErrorwhen chaining methods like.min()after.refine()on az.string()in Zod version 3.23.8. - You provided a workaround using an
ifstatement, but noted it adds complexity. - Sunnylost explained that
.refine()returns aZodEffect, not aZodType, and suggested using.pipe()for chaining. - The discussion indicates a need for more intuitive chaining support in Zod.
Next Steps:
- Please confirm if this issue is still relevant to the latest version of the Zod repository. If so, you can keep the discussion open by commenting here.
- Otherwise, this issue will be automatically closed in 7 days.
Thank you for your understanding and contribution!
Hi @RalkeyOfficial! I'm Dosu and I’m helping the zod team.
I'm a bot trained on Zod's codebase and issues. The reason you can't chain .min() after .refine() in Zod 3 is that .refine() returns a generic ZodEffects wrapper, which doesn't expose type-specific methods like .min() from ZodString. This is an intentional design decision for extensibility, not a bug or oversight. The workaround you found is necessary in Zod 3, but admittedly not very ergonomic.
This limitation is resolved in Zod 4: refinements now live inside the schema itself, so you can freely chain .min(), .max(), and other methods before or after .refine() as expected. If this chaining pattern is important for your codebase, I recommend upgrading to Zod 4. You can read more about this change in the Zod 4 documentation and changelog, and see related discussion in this issue.
For Zod 3, the best practice is to apply built-in methods like .min() before .refine(), or use another .refine() for additional checks if needed. If this answers your question, please close the issue!
To reply, just mention @dosu.
How did I do? Good | Irrelevant | Incorrect | Verbose | Hallucination | Report 🐛 | Other
Fixed in Zod 4