Building SvelteKit forms with Superforms
Tags: svelte, webdev
Published: 2023-10-18 19:49:38

**Written by [Chiamaka Umeh](https://blog.logrocket.com/author/chiamakaumeh/)✏️**

Due to the ever-evolving nature of software development, creating and managing forms remains a crucial yet often difficult task to achieve. Whether for basic contact forms or intricate data input interfaces, form validation is essential to ensure accurate and secure submissions. This step becomes even more complex as an application grows in size. 

Imagine developing an app with multiple forms, each demanding unique data formats, required fields, and distinct validation rules. Quickly, the validation logic can become hard to manage. Ensuring consistency in error messages, data handling, and user experience becomes a difficult task. 

Superforms, a SvelteKit library, helps with this. This library offers a comprehensive solution to the challenges posed by form validation complexity. Leveraging a Zod validation schema as the single source of truth, Superforms centralizes validation rules, ensuring consistency across the entire application. This approach eliminates code duplication and offers a solution for server and client validation. 

In this article, we will learn how to set up and use Superforms in our Svelte applications. We’ll cover the following:

*   [Features of the Superforms library](#features-superforms-library)
*   [Building a form with Superforms](#building-form-superforms)
*   [Server-side validation](#server-side-validation)
    *   [Debugging Superforms](#debugging-superforms)
    *   [Posting data back to the server](#posting-data-back-server)
    *   [Using the `use:enhance` feature](#using-use-enhance-feature)
*   [Client-side validation](#client-side-validation)
    *   [Constraints](#constraints)

## Features of the Superforms library <a name="features-superforms-library">

The Superforms library seamlessly validates data on both the server and client using Zod, with output that seamlessly integrates into the client. The library also offers auto-centering and auto-focusing on erroneous form fields, which enhances user-friendly interactions. 

Superforms has the ability to detect tainted/dirty forms, safeguarding against data loss when users navigate away from unsaved forms. The library transcends FormData limitations for complex data structures, transparently dispatching forms as devalued JSON. 

Additionally, Superforms generates default form values based on validation schemas. It supports intricate nested data structures, snapshots, and accommodates multiple forms on a single page. It works seamlessly on both server-rendered and single-page applications. 

Superforms offers proxy objects for hassle-free data conversion to and from string representations. Introducing real-time, client-side validators, it provides instant user feedback for enhanced usability. Its extensive event integration provides complete control over validation data, and ActionResult, offering the option to halt updates at any stage.

## Building a form with Superforms <a name="building-form-superforms">

Below is a simple unvalidated form for receiving user information. In this section, we will install Superforms and validate our user form with it:

```typescript
//src/routes/+page.svelte

<script lang="ts">

</script>

  <div class='form-wrapper'>
        <form method="POST">
            <label for="name">First Name</label>
            <input
                type="text"
                name="name"
            />

            <label for="name">Last Name</label>
            <input
                type="text"
                name="name"
            />

            <label for="email">E-mail</label>
            <input
                type="email"
                name="email"
            />

            <label for="email">Employee Number</label>
            <input
                type="number"
                name="employeeNumber"
            />

            <div><button>Submit</button></div>
        </form>
  </div>

  <style>
    .form-wrapper{
            display: flex;
            justify-content: center;
    }
    button {
            background-color: #008001;
            border-radius: 5px;
            border: none;
            padding: 10px;
          }
      button:hover {
            background-color: #AAD922;
      }
  </style>
```

If you are adding Superforms to an existing project, install it using either of the following commands:

```yarn
pnpm i -D sveltekit-superforms zod
```

```yarn
npm i -D sveltekit-superforms zod
```

If you are creating a new SvelteKit project, install it with this command before installing Superforms: `npm create svelte@latest`.

## Server-side validation <a name="server-side-validation">

This section explores how to validate a form on the server side of a SvelteKit application. The first thing we do is create a schema using [Zod](https://zod.dev/?id=introduction), which we installed earlier. The schema represents the form data:

```typescript
//src/routes/+page.server.ts

import { z } from "zod"

const userSchema = z.object({
  firstName: z.string().min(1),
  lastName: z.string().min(1),
  email: z.string().email(),
  employeeNumber: z.number().min(1)
})
```

Next, we’ll initialize the form in the `load` function using Superforms' server API, `superValidate`. `superValidate` takes in the schema we described earlier:

```typescript
//src/routes/+page.server.ts

import { z } from "zod"
import { superValidate } from 'sveltekit-superforms/server';

const userSchema = z.object({
  firstName: z.string().min(1),
  lastName: z.string().min(1),
  email: z.string().email(),
  employeeNumber: z.number().min(1)
})

export const load = (async () => {
  // Server API:
  const form = await superValidate(userSchema);

  // Always return { form } in load and form actions.
  return { form };
});
```

With this, we are now forwarding the validation data to the client. Our next step involves retrieving this data using the client-side API called `superForm` in the Svelte page with `export let data;`. After that, we destructure `form` from `superForm`. 

See below for the code:

```typescript
//src/routes/+page.svelte

<script lang="ts">
import { superForm } from "sveltekit-superforms/client"
export let data:

const { form } = superForm(data.form)

</script>
```

`form` is a store containing the properties of our `userSchema` schema. We use the prefix `$` before `form` to access the form store. Next, we bind the values of each input field to its corresponding form property like so: `bind:value={$form.firstName}`. We do this for all the fields.

## Debugging Superforms

Before we move on to posting the form data to the server, let me quickly demonstrate a cool component provided by Superforms for dev debugging called `SuperDebug`. This component takes `form` as a prop and can be used to show what is happening with the form in real time. It also has a status code that displays `error` when the form validation fails, and `success` in the absence of any errors. It should only be used during development. 

This is how to import and use `SuperDebug`:

```typescript
import SuperDebug from "sveltekit-superforms/client/SuperDebug.svelte"
```

```typescript
<SuperDebug data={form}/>
```

It shows the object at the top of the page so you see the changes happening to the form: ![Changes Happening To The Form](https://blog.logrocket.com/wp-content/uploads/2023/10/changes-happening-form.png)

### Posting data back to the server <a name="posting-data-back-server">

To post form data back to the server, we’ll create a form action. Also, we'll continue to use the `superValidate` function, but this time we'll fill it with `FormData`, which holds the form data. 

There are a few ways to do this. The first is by using the `event` object, which also has the request:

```typescript
export const actions = {
      default: async (event) => {
            const form = await superValidate(event, userSchema);
            console.log(form);
            return { form };
      }
};
```

The second way is by using the `request` parameter, which includes `FormData`:

```typescript
export const actions = {
      default: async ({ request }) => {
            const form = await superValidate(request, userSchema);
            console.log(form);
            return { form };
      }
};
```

Now let’s fill out and submit the form, and then inspect the console to see the form data being sent to the server from the client: ![Form Data Being Sent From The Server To The Client](https://blog.logrocket.com/wp-content/uploads/2023/10/form-data-being-sent-server-client.png)

```typescript
{
  id: 'h7b6xj',
  valid: true,
  posted: true,
  errors: {},
  data: {
    firstName: 'John ',
    lastName: 'Doe',
    email: 'johndoe@gmail.com',
    employeeNumber: '09719293'
  },
  constraints: {
    firstName: { minlength: 1, required: true },
    lastName: { minlength: 1, required: true },
    email: { required: true },
    employeeNumber: { minlength: 1, required: true }
  }
}
```

The code above is what you get back from `superValidate`. It also gives you everything you need to further customize the form:

*   `id`: This is used to identify the schema, which is useful when dealing with multiple forms on a page
*   `valid`: This informs you if the form validation was successful or not. This information is useful for both server-side and event-related actions
*   `posted`: This tells us whether the data was sent through the form action or not
*   `data`: This is an object containing the posted data. With this property, you can easily check if the user data is valid or not. If not, it can be sent back to the client using the “fail” approach
*   `errors`: This object contains all the validation errors maintaining the same structure as the data object
*   `message`: This field is set to pass a status message for further clarity
*   `constraints`: This is also an object that stores normal HTML validation constraints, which can be applied to input fields thereby adding another layer of validation

Now we can add logic to return an error with a code of `400`:

```typescript
if (!form.valid) {
    return fail(400, {
        form
    })
}
```

Now if we try to skip a field in the form, we will find that the error object is no longer empty:

```typescript
{
  id: 'h7b6xj',
  valid: false,
  posted: true,
  errors: {
    lastName: [ 'String must contain at least 1 character(s)' ],
    email: [ 'Invalid email' ],
    employeeNumber: [ 'String must contain at least 1 character(s)' ]
  },
  data: { firstName: 'ww', lastName: '', email: '', employeeNumber: '' },
  constraints: {
    firstName: { minlength: 1, required: true },
    lastName: { minlength: 1, required: true },
    email: { required: true },
    employeeNumber: { minlength: 1, required: true }
  }
}
```

Notice this status code is no longer showing `200` but `400`: ![The New Status Code](https://blog.logrocket.com/wp-content/uploads/2023/10/new-status-code.png) With the updated error object, we can now import the error in the client and show the users:

```typescript
const { form, errors } = superForm(data.form)
```

Then, we can use it to show the appropriate error under each input like so:

```typescript
{#if $errors.firstName} {$errors.firstName} {/if}
```

This is the updated code for `//src/routes/+page.svelte` at this point:

```typescript
//src/routes/+page.svelte

<script lang="ts">
    import { superForm } from "sveltekit-superforms/client"
    import SuperDebug from "sveltekit-superforms/client/SuperDebug.svelte"

    export let data;

    const { form, errors } = superForm(data.form)

</script>

<SuperDebug data={form}/>

<div class='form-wrapper'>
    <form method="POST">
        <div>
                <label for="name">First Name</label>
                <input
                        type="text"
                        name="firstName"
                        bind:value={$form.firstName}
                />
                {#if $errors.firstName}
                        <p class='error-para'>{$errors.firstName}</p>
                {/if}
        </div>

        <div>
                <label for="name">Last Name</label>
                <input
                        type="text"
                        name="lastName"
                        bind:value={$form.lastName}
                />
                {#if $errors.lastName}
                        <p class='error-para'>{$errors.lastName}</p>
                {/if}
        </div>

        <div>
                <label for="email">E-mail</label>
                <input
                        type="email"
                        name="email"
                        bind:value={$form.email}
                />
                {#if $errors.email}
                        <p class='error-para'>{$errors.email}</p>
                {/if}
        </div>

        <div>
                <label for="email">Employee Number</label>
                <input
                        type="number"
                        name="employeeNumber"
                        bind:value={$form.employeeNumber}
                />
                {#if $errors.employeeNumber}
                        <p class='error-para'>{$errors.employeeNumber}</p>
                {/if}
        </div>
        <div><button>Submit</button></div>
    </form>
</div>

<style>
    .form-wrapper{
            display: flex;
            justify-content: center;
    }
    button {
        background-color: #008001;
        border-radius: 5px;
        border: none;
        padding: 10px;
    }
    button:hover {
        background-color: #AAD922;
    }
    .error-para{
        color: red;
        font-size: 12px;
    }
</style>
```

![Errors In Our Form](https://blog.logrocket.com/wp-content/uploads/2023/10/form-errors.png)

### Using the `use:enhance` feature <a name="using-use-enhance-feature">

When we use the "enhance" feature from `superForm`, it brings enhanced client-side interactivity to forms in a SvelteKit application. When you use the `enhance` feature, various client-side features become available, such as events, timers, and auto-error focus. These enhancements enhance the user experience by providing real-time feedback and smoother interactions. 

Notably, the `use:enhance` action doesn't take any arguments. Instead, events are employed to interact with the default SvelteKit `use:enhance` parameters and other functionalities. For a deeper understanding of `enhance` and how events work in this context, check out the [official Superforms documentation](https://superforms.rocks/concepts/enhance). 

It's essential to acknowledge that without using `use:enhance`, the form remains static, lacking the enhanced client-side behavior. Only the `constraints` and `resetForm` functionalities will operate in this scenario.

## Client-side validation <a name="client-side-validation">

In this section, we'll explore how you can utilize either a Zod schema or a Superforms validation object to achieve thorough real-time validation directly on the client side.

### Constraints

To make use of the existing browser constraints, simply import the $constraints store from `superForm` and use it in the appropriate fields. Import it with the following line of code:

```typescript
const {form, errors, enhance, constraints} = superForm(data.form)
```

And then apply them to each input like this:

```typescript
{...$constraints.firstName}
```

This is the result below: ![Applying Constraints To Our Form](https://blog.logrocket.com/wp-content/uploads/2023/10/applying-constraints-form.png) The browser's built-in validation might feel a bit limited. For instance, it might be challenging to manage where and how error messages appear. Instead, we have the option to configure certain settings for personalized real-time validation with Superforms. This is how to do that:

```typescript
const { form, enhance, constraints, validate } = superForm(data.form, {
  validators: AnyZodObject | {
    field: (value) => string | string[] | null | undefined;
  },
  validationMethod: 'auto' | 'oninput' | 'onblur' | 'submit-only' = 'auto',
  defaultValidator: 'keep' | 'clear' = 'keep',
  customValidity: boolean = false
})
```

The most convenient and advisable way to perform a client-side validation is by configuring the “validators” option with the Zod schema we use on the server side. But we could also use a Superforms validation object. The object corresponds to the form’s keys and has a function that takes the field value and input. The function returns a string or an array of strings when there is an error with the fields and returns null or undefined if the fields are valid. 

Below is an example of client-side validation with both a Superforms validation object and the previously defined `userSchema` Zod schema:

```typescript
//src/routes/+page.svelte

const userSchema = z.object({
          firstName: z.string().min(1),
      lastName: z.string().min(1),
      email: z.string().email(),
      employeeNumber: z.string().min(1)
});
// validate the form with our previously defined userSchema zod schema:
const { form, errors, enhance } = superForm(data.form, {
  validators: userSchema;
});

// validate the length of the firstName field with Superforms validation object:

const { form, errors, enhance, constraints } = superForm(data.form, {
    validators: {
      firstName: (firstName) => firstName.length < 3 ? 'Name must be at least 3 characters' : null,
    }
});

```

The image below demonstrates client validation with our previously defined `userSchema` Zod schema: ![Client Validation With Our Previously Defined UserSchema Zod Schema](https://blog.logrocket.com/wp-content/uploads/2023/10/client-validation-with-previously-defined-userschema-zod-schema.png) The image below demonstrates client validation using the Superforms validation object: ![Client Validation With Our Previously Defined UserSchema Zod Schema](https://blog.logrocket.com/wp-content/uploads/2023/10/client-validation-superforms-validation-object.png) As you can see, any one of the choices works. There are many more [customizations that can be done on the client side](https://superforms.rocks/concepts/client-validation) to validate forms.

## Conclusion

Superforms simplifies form validation in SvelteKit applications, streamlining complexity, enhancing user experience, and delivering powerful client-side interactions. Throughout this article, we've seen and demonstrated how Superforms helps to make the form validation process a lot simpler, thereby making it an important tool for developers. The code used in the tutorial can be found [here](https://stackblitz.com/edit/sveltekit-superforms-1-tutorial-vfw4cv?file=src%2Froutes%2F%2Bpage.svelte).

---

## Get set up with LogRocket's modern error tracking in minutes:

1. Visit https://logrocket.com/signup/ to get an app ID.
2. Install LogRocket via NPM or script tag. `LogRocket.init()` must be called client-side, not server-side.

NPM:
```bash
$ npm i --save logrocket 

// Code:

import LogRocket from 'logrocket'; 
LogRocket.init('app/id');
```
Script Tag:
```javascript
Add to your HTML:

<script src="https://cdn.lr-ingest.com/LogRocket.min.js"></script>
<script>window.LogRocket && window.LogRocket.init('app/id');</script>
```

3.(Optional) Install plugins for deeper integrations with your stack:
*   Redux middleware
*   ngrx middleware
*   Vuex plugin

[Get started now](https://lp.logrocket.com/blg/signup)