Schema mapping
What each zod schema becomes as a control and its attributes, along with what an empty control submits, the checks the browser does not run, and the schemas it cannot take.
On this page
Control types
The type in input for each kind of schema:
z.string():type="text"z.email(),z.url():type="email",type="url"z.iso.date(),z.iso.time():type="date",type="time"z.iso.datetime({ local: true }):type="datetime-local"z.coerce.number():type="number"z.boolean(),z.literal(true):type="checkbox"z.stringbool():type="checkbox", with thevaluea checked box submitsz.file():type="file"- A string with
input: "password"metadata:type="password" z.enum([…]): notype; spread it onto a<select>z.array(z.enum([…])): notype; one checkbox per option- Anything else (
z.uuid(),z.coerce.date(), …):type="text"
Stacking a check such as .regex() on z.email() or z.iso.date() does not change the control type.
Constraints and attributes
Schema constraints become these attributes:
.min(),.max()on a string:minLength,maxLength.regex():pattern, when it is the only regex and has no flags.min(),.max()on a number:min,max.multipleOf(): that value asstep..int()alone givesstep="1", and neither givesstep="any".mime()on a file:accept- When the schema rejects what an empty control submits:
required
What an empty control submits
This decides both whether required is emitted and what parseForm hands the schema.
- A text control:
"" - An unchecked
z.boolean()checkbox:false - A number, a
z.coerce.bigint(), a file or a choice: nothing (undefined) - An unchecked
z.stringbool()box: nothing (undefined) - A checkbox group with nothing checked:
[]
Checks the browser does not run
These cannot be expressed as HTML attributes, so they are listed in dropped. The server still runs every one.
- A
.refine()on the whole schema - An exclusive bound on a float (
.gt(),.lt()) - A regex with flags, and several regexes on one string
- A regex on a control that ignores
pattern, such astype="date" .mime()on a file, and its size bounds.min()on a checkbox group; declareminCheckedto run it in the browser too- Constraints behind a
.transform()or az.custom(), which cannot be read
Pitfall
A .refine() on a single field, a nested object or a row is not listed in dropped yet. The browser lets it through without a word, and only the server checks it.
Schemas it refuses
formFields and parseForm throw, with the reason, on these:
z.number(),z.bigint(),z.date(),z.literal(1): every value arrives as a string, so nothing could pass. Usez.coercez.record, tuples, nullable objects, a repeat inside a repeat: there is no single name to submit under- Keys holding
.or brackets: they cannot be told apart from the path separators z.stringbool().default(true): unchecking the box could never submitfalse
What it does not do yet
- Several files in one control.
z.array(z.file())derives repeated single-file rows - Rules on fields inside repeated rows
- Input masking. Rewriting the value works, but managing the caret is a separate problem from wiring a form