If you want to validate an array of objects from this Schema

const fieldsOfEng = [
  {
    id: "ELECTRICAL",
    name: "Electrical",
    experience: 1,
  },
  {
    id: "MECHANICAL",
    name: "Mechanical",
    experience: undefined,
  },
];

I'd do it like this

const userInfoSchema = z.object({
  id: z.string(),
  name: z.string(),
  experience: z.number().optional()
})

// Now add this object into an array
const usersInfoSchema = z.array(userInfoSchema)
Answer from Ark on Stack Overflow
Top answer
1 of 3
43

If you want to validate an array of objects from this Schema

const fieldsOfEng = [
  {
    id: "ELECTRICAL",
    name: "Electrical",
    experience: 1,
  },
  {
    id: "MECHANICAL",
    name: "Mechanical",
    experience: undefined,
  },
];

I'd do it like this

const userInfoSchema = z.object({
  id: z.string(),
  name: z.string(),
  experience: z.number().optional()
})

// Now add this object into an array
const usersInfoSchema = z.array(userInfoSchema)
2 of 3
8

This answer is an update on my progress so far. I closer to getting my schema validation working correctly but for some reason it is not submitting. I could solve this faster if I could get react-hook form's errors to output correctly but since I'm using template literals when I'm registering the inputs, the error is just outputting nothing. I DO know there is an error though because my truthy error outputs the "professions:" string that I added in the p-tag.

Data for checkboxes in form (this is the data I'm validating via zod):

const fieldsOfEng = [
  {
    id: "ELECTRICAL",
    name: "Electrical",
    experience: undefined,
  },
  {
    id: "MECHANICAL",
    name: "Mechanical",
    experience: undefined,
  },

Schema for validation: I'm checking that the name they select is included in my fieldsOfEng array and making sure the experience is greater than 1.

const userInfoSchema = object({
professions: z
    .object({
      name: z
        .string()
        .refine((name) =>
          fieldsOfEng.map((field) => field.name).includes(name)
        ),
      experience: z.number().refine((experience) => experience > 1),
    })
    .array(),
});

React hook form:

type userInfoType = z.infer<typeof userInfoSchema>;

  const {
    register,
    watch,
    handleSubmit,
    formState: { errors, isSubmitting },
  } = useForm<userInfoType>({
    resolver: zodResolver(userInfoSchema),
  });

Form: Notice I am using template literals for registering each input. It took me forever to figure this out. So far this is the only way I've been able to get the data to output correctly (via react-hook-form's 'watch' method).

<fieldset>
      <legend>Practicing fields of engineering</legend>
      {fieldsOfEng.map((field, i) => {
        return (
          <div key={field.id}>
            <div>
              <input
                {...register(`professions.${i}.name`)}     <--REGISTERED HERE
                value={field.name}
                type="checkbox"
              />
              <div>
                <input
                  {...register(`professions.${i}.experience`)}    <--REGISTERED HERE
                  type="number"
                  value={field.experience}
                  placeholder="Yrs Experience"
                />
              </div>
            </div>

            <div>
              {errors.professions && (
                <p>
                  professions:
                  {errors.professions.message}
                </p>
              )}
            </div>
          </div>
        );
      })}
    </fieldset>
🌐
GitHub
github.com › edmundhung › conform › discussions › 288
[Remix+Zod] How to deal with a user-managed array of objects? · edmundhung/conform · Discussion #288
// Assume you are managing the state in useState() const [items, setItems] = useState(...) // Just send the array to the server as a JSON <input type="hidden" name={fields.items} value={JSON.stringify(items)} /> // Then parse the JSON in your zod schema through a preprocess const schema = z.object({ // ... items: z.preprocess( value => JSON.parse(value), z .object({ title: z.string({ required_error: 'Title is required' }), description: z.string({ required_error: 'Description is required' }), }) .array() .min(1, 'Please select at least 1 items'), }); One thing to note here is that Conform map zod errors based on its internal path. If the title of the first item is missing, the required message wont show up on your fields.items.errors as it maps to items instead of items[0].title.
Author   edmundhung
Discussions

how can I validate an array of objects where each item should contain only one property ?
how can I validate an array of objects where each item should contain only one property ? More on github.com
🌐 github.com
1
1
March 29, 2023
typescript - Is there a way to have an array of objects with some of them literals? - Stack Overflow
I'm thinking about the following validation with zod and I have no clue on how to do it (or if it's possible with zod). I want an array of objects, all with the same shape, with some of them with l... More on stackoverflow.com
🌐 stackoverflow.com
Please help - deadline tmrw: How to dynamically add array of objects to React Hook Form (TS + ShadCN + Zod)?
usefieldarray should only be used with array of objects. RHF automatically assigns an id prop if it doesn't exist on each object, and not following that rule can cause a bunch of issues. You can pass validation rules on the usefieldarray but it doesn't always work as expected. The trick with RHF is to use controlled components (using the register or Controller) and let the library manage the state of the form. In other words, in most cases you shouldn't have to use onChange, unless you have interdependent components. What works really well when using a 3rd party is to wrap the components with a Controller (or the hook). I'm not sure about those ShadCN components if they are made explicitly for RHF. One thing to note also is you're doing your validation on the onSubmit, there's other modes in the useForm, onBlur, onChange, onTouched. Often times you'll need to do some mapping when settings the default values, and then remap to what your server expects when submitting. For example for the arrays if they are primitive you need to map them both ways to objects. I would very much like to be more helpful but I'm on my phone, hopefully I put you on some tracks. Good luck! More on reddit.com
🌐 r/react
9
9
February 16, 2024
Array of objects with some of them literals
How are you? I'm thinking about the following validation with zod and I have no clue on how to do it (or if it's possible with zod). I want an array of objects, all with the same shape of c... More on github.com
🌐 github.com
5
October 29, 2021
🌐
Zod
zod.dev › api
Defining schemas | Zod
Schemas represent types, from simple primitive values to complex nested objects and arrays. import * as z from "zod"; // primitive types z.string(); z.number(); z.bigint(); z.boolean(); z.symbol(); z.undefined(); z.null(); To coerce input data to the appropriate type, use z.coerce instead: z.coerce.string(); // String(input) z.coerce.number(); // Number(input) z.coerce.boolean(); // Boolean(input) z.coerce.bigint(); // BigInt(input) The coerced variant of these schemas attempts to convert the input value to the appropriate type.
🌐
Tecktol
tecktol.com › home › zod › zod arrays: from basics to array of objects validation
Zod Arrays: From Basics to Array of Objects Validation - Tecktol
March 30, 2026 - Learn how Zod handles arrays — from simple string/number arrays to complex array of objects validation, type inference, safe parsing, refinements, and best practices.
🌐
Total TypeScript
totaltypescript.com › tutorials › zod › zod-section › array › solution
Create an Array of Custom Types | Total TypeScript
June 8, 2023 - Matt Pocock: 0:00 The correct way to represent this in Zod is with z.array. Now, what this does is we're basically creating an object which references another object, which is pretty cool. You can compose these together.
🌐
Nicoespeon
nicoespeon.com › en › 2026 › 03 › decoding-arrays-with-zod
The Proper Way to Decode Arrays (with Zod) — @nicoespeon's blog
March 23, 2026 - import { z } from 'zod' const DROPPED = Symbol('dropped') function arrayFromFallible<T extends z.ZodType>(schema: T) { return z .array(schema.catch(DROPPED as never)) .transform(items => items.filter(item => item !== DROPPED) as z.output<T>[]) } Instead of z.array(Todo), you write arrayFromFallible(Todo). That’s it. .parse() returns a type-safe array with the invalid items silently dropped. const Todo = z.object({ id: z.number(), title: z.string(), completed: z.boolean(), }) const Todos = arrayFromFallible(Todo) const result = Todos.parse([ { id: 1, title: 'Buy milk', completed: false }, { id: 'oops', title: null, completed: 'maybe' }, { id: 2, title: 'Write blog post', completed: true }, ]) // ✅ [{ id: 1, ...
Find elsewhere
🌐
CodeSandbox
codesandbox.io › s › react-hook-form-zod-with-array-of-objects-field-array-usefieldarray-8xh3ry
React Hook Form + Zod with array of objects (field ...
This website utilizes cookies to enable essential site functionality and analytics. You may change your settings at any time or accept the default settings. You may close this banner to continue with only essential cookies. Read more about this in our privacy and cookie statement
🌐
Zod
zod.dev
Intro | Zod
Zod is a TypeScript-first validation library. Using Zod, you can define schemas you can use to validate data, from a simple string to a complex nested object.
🌐
Conform
conform.guide › complex-structures
Conform / Nested object and Array
1import { useForm } from '@conform-to/react'; 2import { parseWithZod } from '@conform-to/zod'; 3import { z } from 'zod'; 4 5const schema = z.object({ 6 tasks: z.array(z.string()), 7}); 8 9function Example() { 10 const [form, fields] = useForm({ 11 onValidate({ formData }) { 12 return parseWithZod(formData, { schema }); 13 }, 14 }); 15 const tasks = fields.tasks.getFieldList(); 16 17 return ( 18 <form id={form.id}> 19 <ul> 20 {tasks.map((task) => ( 21 <li key={task.key}> 22 {/* Set the name to `task[0]`, `tasks[1]` etc */} 23 <input name={task.name} /> 24 <div>{task.errors}</div> 25 </li> 26 ))} 27 </ul> 28 </form> 29 ); 30} You can also combine both getFieldset() and getFieldList() for nested array.
🌐
GitHub
github.com › orgs › react-hook-form › discussions › 11381
Zod unique array/array of object validation with superRefine + shadcn · react-hook-form · Discussion #11381
January 10, 2024 - }), VariantsRegistryOption: z .array( z.object({ option_name: z.string(), option_values: z .array( z.object({ option_value: z.string().min(2) }) ) .nonempty() .superRefine((items, ctx) => { const uniqueItemsCount = new Set(items.map((value) => value.option_value)).size; const errorPosition = items.length - 1; if (uniqueItemsCount !== items.length) { ctx.addIssue({ code: z.ZodIssueCode.custom, message: 'Duplicate values are not allowed.', path: [errorPosition, 'option_value'] }); } }) }) ) .superRefine((items, ctx) => { const uniqueItemsCount = new Set(items.map((value) => value.option_name)).s
🌐
Reddit
reddit.com › r/react › please help - deadline tmrw: how to dynamically add array of objects to react hook form (ts + shadcn + zod)?
r/react on Reddit: Please help - deadline tmrw: How to dynamically add array of objects to React Hook Form (TS + ShadCN + Zod)?
February 16, 2024 -

I'm fairly new to React and I'm trying to build a dynamic form that includes an array of route stops using RHF + Typescript + Zod + ShadCN. I've reviewed the docs and have been able to implement simple text and []string fields, but am blocked at the section of a dynamic object array.

I followed the instructions here to add the ShadCN form components. Below is the relevant code. I'm using useFieldArray and it makes perfect sense for string arrays, but I can't get it to work for an object array. I've shown the different things I've tried in the comments of the field. When I try {...form.register(stops.${index}.city)}, it correctly renders the default values, but the submit button doesn't do anything and the validation will throw undefined or false negatives for wrong input. The other fields work just fine. I would greatly appreciate any help on this! Thank you for your time!

import "./styles.css";
import { zodResolver } from "@hookform/resolvers/zod";
import { useFieldArray, useForm } from "react-hook-form";
import * as z from "zod";

import { Button } from "./button";
import {
  Form,
  FormControl,
  FormDescription,
  FormField,
  FormItem,
  FormLabel,
  FormMessage,
} from "./form";
import { Input } from "./input";

const stopObj = z.object({
  order: z.coerce.number(),
  zip: z.string(),
  city: z.string({ required_error: "This field is required." }),
  state: z
    .string({ required_error: "This field is required." })
    .max(2, "2-letter state abbreviation"),
  country: z.coerce
    .string()
    .max(2, "Two-letter country code (ISO 3166-1 alpha-2)"),
});

const formSchema = z.object({
  firstName: z.string({ required_error: "This field is required." }),
  currency: z.enum(["USD", "CAD"]),
  stops: z
    .array(stopObj)
    .min(2, "2 stops minimum required (pickup and dropoff)"),
});

export default function App() {
  const form = useForm<z.infer<typeof formSchema>>({
    resolver: zodResolver(formSchema),
    defaultValues: {
      firstName: "John",
      currency: "USD",
      stops: [
        { order: 1, city: "New York", state: "NY", zip: "02116", country: "US" },
        { order: 2, city: "Austin", state: "TX", zip: "12345", country: "US" },
      ],
    },
  });

  async function onSubmit(values: z.infer<typeof formSchema>) {
    console.log("onSubmit:", values);
  }

  const { fields } = useFieldArray({ name: "stops", control: form.control });

  return (
    <div className="bg-[#305645] w-full justify-center flex">
      <div className="w-3/4 bg-white px-5 py-3 my-20 rounded-lg">
        <Form {...form}>
          <div className="text-center font-medium">
            <h1 style={{ fontSize: "2rem", marginBottom: "1rem" }}>
              Complete Form
            </h1>
          </div>
          <form onSubmit={form.handleSubmit(onSubmit)} className="space-y-4">
            <div className="flex space-x-4 w-full">
              <div className="w-1/2">
                <FormField
                  control={form.control}
                  name="firstName"
                  render={({ field }) => (
                    <FormItem>
                      <FormLabel>First Name</FormLabel>
                      <FormControl>
                        <Input placeholder="John" {...field} />
                      </FormControl>
                      <FormMessage />
                    </FormItem>
                  )}
                />
              </div>

              <FormField
                control={form.control}
                name="currency"
                render={({ field }) => (
                  <FormItem>
                    <FormLabel>Currency</FormLabel>
                    <FormControl>
                      <Input placeholder="USD" {...field} />
                    </FormControl>
                    <FormDescription>USD or CAD.</FormDescription>
                    <FormMessage />
                  </FormItem>
                )}
              />
            </div>

            {/* FIXME how to correctly render and update each stop object?? */}
            {fields.map((field, index) => (
              <FormField
                control={form.control}
                key={field.id}
                name={`stops.${index}`}
                render={({ field }) => (
                  <>
                    <FormItem>
                      <FormLabel>City</FormLabel>
                      <FormDescription />
                      <FormControl>
                        <Input
                          {...field.value.city}
                          // {...form.register(`stops.${index}.city`)}
                          // defaultValue={field.name}
                          // value={field.value.city}
                          // onChange={(e) => field.onChange(e.target.value)}
                          // onBlur={(e) => field.onBlur(e.target.value)}
                        />
                      </FormControl>
                      <FormMessage />
                    </FormItem>

                    <FormItem>
                      <FormLabel>State</FormLabel>
                      <FormDescription />
                      <FormControl>
                        <Input
                          {...form.register(`stops.${index}.state`)}
                          // defaultValue={field.name}
                        />
                      </FormControl>
                      <FormMessage />
                    </FormItem>
                  </>
                )}
              />
            ))}

            <Button
              type="submit"
              className="bg-orange-500 font-extrabold w-full"
            >
              Submit
            </Button>
          </form>
        </Form>
      </div>
    </div>
  );
}

**Screenshot:**Here's a screenshot. The currency enum field works as expected, but the city & state ones do not. Even the behavior shown in the image inconsistent. It took me a few retries for the issue to appear again so I could screenshot. I'm also not getting console or compiler errors.

Update: Here's the closest thing I've found to what I'm trying to do, but I'm struggling to figure out how to translate this to ShadCN UI.

Top answer
1 of 5
14
usefieldarray should only be used with array of objects. RHF automatically assigns an id prop if it doesn't exist on each object, and not following that rule can cause a bunch of issues. You can pass validation rules on the usefieldarray but it doesn't always work as expected. The trick with RHF is to use controlled components (using the register or Controller) and let the library manage the state of the form. In other words, in most cases you shouldn't have to use onChange, unless you have interdependent components. What works really well when using a 3rd party is to wrap the components with a Controller (or the hook). I'm not sure about those ShadCN components if they are made explicitly for RHF. One thing to note also is you're doing your validation on the onSubmit, there's other modes in the useForm, onBlur, onChange, onTouched. Often times you'll need to do some mapping when settings the default values, and then remap to what your server expects when submitting. For example for the arrays if they are primitive you need to map them both ways to objects. I would very much like to be more helpful but I'm on my phone, hopefully I put you on some tracks. Good luck!
2 of 5
2
I also recently encountered something like this but can't pull up my code to see what I did. I'll update this comment once I get back on my desk. Meanwhile I'd suggest check out the devtools and see if the array is actually being updated or not https://www.react-hook-form.com/dev-tools/
🌐
GitHub
github.com › colinhacks › zod › issues › 739
Array of objects with some of them literals · Issue #739 · colinhacks/zod
October 29, 2021 - Example: I need always in the array the objects those with name required1 and required2, and then other objects optionals following the same shape.
Author   colinhacks
🌐
Dev-toolbox
dev-toolbox.tech › home › json to zod schema › convert json arrays of objects to zod array schemas
Convert JSON Arrays of Objects to Zod Array Schemas | DevToolbox
Learn how to convert JSON arrays ... arrays. ... When JSON contains an array of objects, the converter inspects every element to determine a unified schema for the array items, then wraps it in z.array()....
🌐
GitHub
v3.zod.dev
TypeScript-first schema validation with static type inference
TypeScript-first schema validation with static type inference
🌐
Reddit
reddit.com › r/typescript › how would i convert this object of string arrays into a zod type?
r/typescript on Reddit: How would I convert this object of string arrays into a zod type?
November 30, 2023 -

I have the following TypeScript code:

export const allRelicTypeMainStats = {
  [CavernRelicType.Head]: ["HPDelta"],
  [CavernRelicType.Hands]: ["AttackDelta"],
  [CavernRelicType.Body]: [
    "HPAddedRatio",
    "AttackAddedRatio",
    "DefenceAddedRatio",
    "CriticalChanceBase",
    "CriticalDamageBase",
    "HealRatioBase",
    "StatusProbabilityBase",
  ],
  [CavernRelicType.Feet]: [
    "HPAddedRatio",
    "AttackAddedRatio",
    "DefenceAddedRatio",
    "SpeedDelta",
  ],
  [PlanarOrnamentType.PlanarSphere]: [
    "HPAddedRatio",
    "AttackAddedRatio",
    "DefenceAddedRatio",
    "PhysicalAddedRatio",
    "FireAddedRatio",
    "IceAddedRatio",
    "ThunderAddedRatio",
    "WindAddedRatio",
    "QuantumAddedRatio",
    "ImaginaryAddedRatio",
  ],
  [PlanarOrnamentType.LinkRope]: [
    "BreakDamageAddedRatioBase",
    "SPRatioBase",
    "HPAddedRatio",
    "AttackAddedRatio",
    "DefenceAddedRatio",
  ],
} as const;

export type RelicMainStat = (typeof allRelicTypeMainStats)[RelicType][number];

I want to make a zod type that's equivalent of RelicMainStat, with other types that use [...] as const I could use stuff like union, enum, etc. but for this type I can't find a way to convert it into a zod type. Any help? I can't seem to find anything for this. The closest thing I could find is something like nativeEnum, which uses an object as const as well, but mine goes one more level deeper, using T[] as opposed to T.

P.S. PlanarOrnamentType and CavernRelicType are TypeScript enums (dw both don't have conflicting values), and are union'ed to a type named RelicType.

EDIT 1:

I've tried the following, and it seems to work, however idk if it's the best way for something like this:

const ZodRelicMainStat = z.enum([
  ...allRelicTypeMainStats[CavernRelicType.Head],
  ...allRelicTypeMainStats[CavernRelicType.Hands],
  ...allRelicTypeMainStats[CavernRelicType.Body],
  ...allRelicTypeMainStats[CavernRelicType.Feet],
  ...allRelicTypeMainStats[PlanarOrnamentType.PlanarSphere],
  ...allRelicTypeMainStats[PlanarOrnamentType.LinkRope]
] as const)

🌐
GitHub
github.com › colinhacks › zod › discussions › 2937
A schema for an array that includes at least one item of a certain type · colinhacks/zod · Discussion #2937
I imagine something like: const schema = z.any().array().includes(z.string()) Seems like a very common use case (I need it right now for my project). Is there a way to do such thing without refine?
Author   colinhacks
🌐
Medium
rasitcolakel.medium.com › exploring-zod-a-comprehensive-guide-to-powerful-data-validation-in-javascript-typescript-2c4818b5646d
Exploring Zod: A Comprehensive Guide to Powerful Data Validation in JavaScript/TypeScript | by Raşit Çolakel | Medium
June 11, 2023 - Creates a schema that validates a key of an object. const person = z.object({ name: z.string(), }); const personWithName = person.keyOf(); type PersonWithName = z.infer<typeof personWithName>; // "name" const data = personWithName.safeParse("name"); // Success ✅ const data = personWithName.safeParse("age"); // Error ❌ · As you can see, Zod is a powerful schema validation library that is easy to use and has great error messages.
🌐
P6p
zod.p6p.net › en › guide › arrays.html
Arrays | Zod
// Array of objects const User = z.object({ id: z.string(), name: z.string(), }); const UserArray = z.array(User); // Array of union types const mixedArray = z.array(z.union([z.string(), z.number()])); mixedArray.parse(["hello", 42, "world"]); // passes // Nested arrays const matrix = ...