zod
zod copied to clipboard
Although `z.string().date()` check fails, yet the callback to `.superRefine()` is executed.
Consider the following schema.
import { z } from "zod";
export const dateRangeSchema = z.object({
minimum: z.string().date(),
maximum: z.string().date(),
}).superRefine(({ minimum, maximum }, context) => {
console.log({ minimum, maximum }); // for debugging
if (new Date(minimum) > new Date(maximum)) {
context.addIssue({
code: z.ZodIssueCode.too_big,
type: "date",
maximum,
inclusive: true,
path: ["minimum"],
message: "minimum must not be greater than maximum",
});
context.addIssue({
code: z.ZodIssueCode.too_small,
type: "date",
minimum,
inclusive: true,
path: ["maximum"],
message: "maximum must not be lesser than minimum",
});
}
});
I expect when minimum or maximum are invalid date strings, then the callback to .superRefine() won't be executed. However, this is not the case. Consider the following example.
console.log(dateRangeSchema.safeParse({ minimum: "1", maximum: "0" }).error.issues);
The above example prints out the following output to the console.
{ minimum: '1', maximum: '0' }
[
{
code: 'invalid_string',
validation: 'date',
message: 'Invalid date',
path: [ 'minimum' ]
},
{
code: 'invalid_string',
validation: 'date',
message: 'Invalid date',
path: [ 'maximum' ]
},
{
code: 'too_big',
type: 'date',
maximum: '0',
inclusive: true,
path: [ 'minimum' ],
message: 'minimum must not be greater than maximum'
},
{
code: 'too_small',
type: 'date',
minimum: '1',
inclusive: true,
path: [ 'maximum' ],
message: 'maximum must not be lesser than minimum'
}
]
As you can see, although "1" and "0" fail the z.string().date() check, yet the callback to .superRefine() is executed. This is unintuitive behavior. At the very least, it violates the principle of least astonishment. The callback to .superRefine() should only be executed when the input is parsed without any issue.
Hi, @aaditmshah-commversion. I'm Dosu, and I'm helping the Zod team manage their backlog. I'm marking this issue as stale.
Issue Summary:
- You raised a concern about the
.superRefine()callback executing even whenz.string().date()validation fails. - This behavior was unexpected for you and seems contrary to the principle of least astonishment.
- There have been no comments or activity on this issue since it was opened.
Next Steps:
- Please let me know if this issue is still relevant to the latest version of the Zod repository by commenting here.
- If there is no further activity, this issue will be automatically closed in 7 days.
Thank you for your understanding and contribution!
I don't know whether this issue was resolved in Zod v4. However, it still hasn't been fixed in v3.23.8.
Hi @aaditmshah-commversion, thanks for the update! I've tagged @colinhacks to take a look at your concern regarding the .superRefine() callback behavior in Zod v3.23.8.
Hi @aaditmshah-commversion! I'm Dosu and I’m helping the zod team.
I'm a bot trained on Zod's codebase and issues. This behavior is still present in Zod v4: refinements added with .check() (which replaces .superRefine()) will run even if child validations fail, unless you use the new when parameter or abort logic to control when your refinement runs. By default, both field errors and custom refinement errors are reported together—there's no built-in guard to skip object-level refinements if leaf errors exist. This is an intentional design decision in Zod v4 and is not called out as a change in the migration guide or docs (docs, code, issue).
If you want .check() to only run when all fields are valid, you can use the when parameter to conditionally run your refinement, or run base validation first and only run cross-field checks if there are no errors. If you need an example or more details, let me know!
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
Hello @dosu. Thank you for the update. It'll take me a while to migrate my code to Zod v4. I'll keep you posted whether using the new when parameter solves my issue.
You're welcome! Looking forward to hearing how it goes with Zod v4 and the when parameter. If you run into any issues or have questions during migration, just let me know. If your question is answered or the migration resolves your concern, feel free to close the issue.
To reply, just mention @dosu.
How did I do? Good | Irrelevant | Incorrect | Verbose | Hallucination | Report 🐛 | Other
Methods like .date() are just adding additional refinements on top of the z.string() schema. Refinements are considered continuable by default. Zod tries to surface as many errors as possible by default.
You need to add the abort flag if you want Zod to stop parsing if the .date() refinement has failed:
export const dateRangeSchema = z
.object({
minimum: z.string().date({ abort: true }),
maximum: z.string().date({ abort: true }),
})
.superRefine(({ minimum, maximum }, context) => {
@dosu learn
Got it—I've updated my knowledge: in Zod v4, using .date({ abort: true }) on a string field will stop further refinements (like .superRefine() or .check()) from running if the .date() check fails. This lets you prevent cross-field or object-level refinements from executing when a field-level validation fails. I'll use this guidance for future questions about controlling refinement execution order in Zod.
To reply, just mention @dosu.
How did I do? Good | Irrelevant | Incorrect | Verbose | Hallucination | Report 🐛 | Other