---
title: Complex Forms | Advanced
description: Learn how to create complex forms with arrays and objects.
contributors:
  - ulic75
  - hamatoyogi
  - aendel
  - tzdesign
updated_at: '2023-08-21T21:04:20Z'
created_at: '2023-08-21T21:04:20Z'
---

import { Note } from '~/components/note/note';

# Complex Forms

## What are Complex Forms?

Complex forms would typically contain data in a structure that is more than just simple key/value pairs. For example, a list of people who each have their own email, phone and street address. This would essentially be an array of objects.

## Input naming

### Objects

Nested items can be created by separating the items with `.` (dot) in the name. For example, `person.name` would be converted to `{ person: { name: 'sam' } }`.

### Arrays

Arrays, either top-level or nested are created by specifying the zero-based index in the name.  For example `person.pets.0` would be converted to `{ person: { pets: [ 'cat' ] } }`.

### Example

The key to creating a complex form is in the naming of the inputs. Below would be an example of the list described above:
```html
<form>
  <input name="person.0.name" value="Sam">
  <input name="person.0.email" value="sam@complexform.com">
  <input name="person.0.pets.0" value="cat">
  <input name="person.0.pets.1" value="dog">
  <input name="person.0.address.street" value="1234 Example Ave.">
  <input name="person.0.address.city" value="Qwik">
  <input name="person.0.address.state" value="IA">
  <input name="person.0.address.zip" value="00000">
  <input name="person.0.pets.0" value="beaver">

  <input name="person.1.name" value="Bonnie">
  <input name="person.1.email" value="bonnie@hishai.net">
  <input name="person.1.address.street" value="768 Resolution Way">
  <input name="person.1.address.city" value="Jaffa">
  <input name="person.1.address.state" value="IL">
  <input name="person.1.address.zip" value="01948">
</form>
```

#### Output object

After submitting the form the data would be parsed in an object like this:
```json
{
  "person": [
    {
      "name": "Sam",
      "email": "sam@complexform.com",
      "address": {
        "street": "1234 Example Ave.",
        "city": "Qwik",
        "state": "IA",
        "zip": "00000"
      },
      "pets": ["beaver"]
    },
    {
      "name": "Bonnie",
      "email": "bonnie@hishai.net",
      "address": {
        "street": "768 Resolution Way",
        "city": "Jaffa",
        "state": "IL",
        "zip": "01948"
      }
    }
  ]
}
```

## Using with Actions

Complex forms can be validated using zod$ with routeAction$ and globalAction$. Continuing with the previous examples it would look like this:

```tsx
export const action = routeAction$(
  async (person) => {
    return { success: true, person, };
  },
  // Zod schema is used to validate the FormData
  zod$({
    person: z.array(
      z.object({
        name: z.string(),
        email: z.string().email(),
        address: z.object({
          street: z.string(),
          city: z.string(),
          state: z.string(),
          zip: z.coerce.number()
        }),
        pets: z.array(z.string())
      })
    ),
  })
);
```

### Field errors also in dot notation (almost)

If you use dot notation, the error messages are also returned in dot notation in the `fieldErrors` property. This has the advantage that the input name and fieldError key match.

For the this action:
```tsx
export const addPersonAction = routeAction$(
  async person => {
    return { success: true, person };
  },
  // Zod schema is used to validate the FormData
  zod$({
    person: z.object({
      name: z.string(),
      email: z.string().email(),
      address: z.object({
        street: z.string(),
        city: z.string(),
        state: z.string(),
        zip: z.coerce.number(),
      }),
      pets: z.array(z.string()),
    }),
  })
);
```

You would get this kind of `fieldErrors` if all are wrong:

```ts
{
  "person.name": "Invalid string",
  "person.email": "Invalid email",
  "person.address.street": "Invalid string",
  "person.address.city": "Invalid string",
  "person.address.state": "Invalid string",
  "person.address.zip": "Invalid number",
  "person.pets[]": ["Required"]
}
```

If you make person an array, the error messages will switch to a slightly different notation as:

```ts
{
  "person[].name": ["Invalid string"],
  "person[].email": ["Invalid email"],
  "person[].address.street": ["Invalid string"],
  "person[].address.city": ["Invalid string"],
  "person[].address.state": ["Invalid string"],
  "person[].address.zip": ["Invalid number"],
  "person[].pets[]": ["Required"]
}
```

<Note>If you completely forget to assign a value to an array type, lets say you completely forget person in the form. The error is then in `fieldErrors["person[]"]`.</Note>

This way you can easily match the error messages to the input names like this:

```tsx
export const useAddPersonAction = routeAction$(
  async person => {
    console.log(person);
    return { success: true, person };
  },
  zod$({
    person: z.object({
      name: z.string().min(2),
      email: z.string().email(),
      address: z.object({
        street: z.string().min(2),
        city: z.string().min(2),
        state: z.string().min(2),
        zip: z.coerce.number(),
      }),
      pets: z.array(z.string().min(2)),
    }),
  })
);

export default component$(() => {
  const testAction = useAddPersonAction();

  const renderError = (errorMessage: string | undefined) => {
    if (!errorMessage) return null;
    return <p class="error">{errorMessage}</p>;
  };

  return (
    <Form action={testAction}>
      <input type="email" name="person.email" placeholder="Email" />
      {renderError(testAction.value?.fieldErrors?.["person.email"])}
      <input type="text" name="person.name" placeholder="Name" />
      {renderError(testAction.value?.fieldErrors?.["person.name"])}
      <input type="text" name="person.address.street" placeholder="Street" />
      {renderError(testAction.value?.fieldErrors?.["person.address.street"])}
      <input type="text" name="person.address.city" placeholder="City" />
      {renderError(testAction.value?.fieldErrors?.["person.address.city"])}
      <input type="text" name="person.address.state" placeholder="State" />
      {renderError(testAction.value?.fieldErrors?.["person.address.state"])}
      <input type="text" name="person.address.zip" placeholder="Zip" />
      {renderError(testAction.value?.fieldErrors?.["person.address.zip"])}
      <input type="text" name="person.pets.0" placeholder="Pet 1" />
      {renderError(testAction.value?.fieldErrors?.["person.pets[]"]?.[0])}
      <button>Send</button>
    </Form>
  );
});
```
For this example the `fieldErrors` look like this:

```json
{
  "person.name": "String must contain at least 2 character(s)",
  "person.email": "Invalid email",
  "person.address.street": "String must contain at least 2 character(s)",
  "person.address.city": "String must contain at least 2 character(s)",
  "person.address.state": "String must contain at least 2 character(s)",
  "person.pets[]": [
    "String must contain at least 2 character(s)"
  ]
}
```

