Getting form body data in SvelteKit actions/server pages
Tags: svelte, sveltekit, javascript, webdev
Published: 2021-04-09 23:23:45

**NOTE Aug 31, 2020:** I've updated this guide to work with SvelteKit pre-1.0 release (specifically `1.0.0-next.431`) but this should work with most of the recent versions.

Note that [according to the docs](https://kit.svelte.dev/docs/routing#page-actions) SvelteKit page actions will likely change pre-1.0 release, so keep that in mind.

---

If you've setup a form in SvelteKit and now you want to submit it to an endpoint (like `+server` or `+page.server`) but you don't know how to get the data out of the `response` and work with it, then this article is for you!

---

## The Problem

Suppose we have an HTML form like this and we want to `POST` it's content to our `+page.server.ts` file at `/newsletter`:

```html
<!-- src/routes/newsletter/+page.svelte -->
<form
  method="post"
  action="/newsletter"
  enctype="multipart/form-data"
>
  <input type="text" name="name" />
  <input type="email" name="email" />
  <input type="file" name="image" accept="image/*" />
  <button type="submit">Submit</button>
</form>
```

Now the question is, how do we get the data out of the `request` we get in our `+page.server` file?

---

## Accessing form data

To extract data from the request's, we need to grab the `request` from our action, extract the `FormData` from it and then use [the methods that exist on FormData](https://developer.mozilla.org/en-US/docs/Web/API/FormData) to get our data.

Here is somewhat exhaustive list of all the ways to get data out of your form:

```ts
// src/routes/newsletter/+page.server.ts
import type { Action } from "./$types";

export const POST: Action = async ({ request }) => {
  // Grab the form data from the request
  const values = await request.formData();

  // Get a value from the form:
  const name = values.get("name") as string;

  // Getting a file file:
  const file = values.get("image") as File;

  // Get the text value of the file:
  const text = await file.text();

  // Get an array of values (useful for checkboxes and selects):
  const flavors = values.getAll("ice-cream-flavors") as string[];
  // ["vanilla", "toffee", "caramel"]

  // Check if a value exists (useful for boolean checkboxes):
  const agreed = values.has("agree-to-terms");
  // true

  // Get all items in the form in an "entries" type array:
  const items = [...values.entries()];
  // [ [ "name": "Rich Harris" ], [ "hobbies", "svelte" ], [ "hobbies": "journalism" ] ]

  // Get each keys:
  const keys = [...values.keys()];
  // [ "name", "hobbies", "hobbies" ]

  // Get all values:
  const vals = [...values.values()];
  // [ [ "Rich Harris" ], [ "svelte" ], [ "journalism" ] ]

  // And to get all the form data as an object:
  const obj = Object.fromEntries(values.entries());
  // { name: "Rich Harris", hobbies: "journalism" }
  // Note here how any grouped values from multi-selects or checkboxes will only return the last value received.
  // I recommend against using this in those (or most) cases.

  return { location: "/" };
};
```

Note, you should be able to do the same thing with a `+server.ts` page, but you will need to change the type signature if you're using TypeScript to use `RequestHandler`.

Now you should be able to work with your HTML form data, high five! 🙏

---

## Going further

### Form helper function

If you're like me, you'd rather just have a nice little object to play with of all your form data. If you want something like this, try out the following helper function to parse your form data and modify as desired:

```ts
// src/lib/form-helpers.ts
type StructuredFormData =
  | string
  | boolean
  | number
  | File
  | StructuredFormData[];

export function formBody(body: FormData) {
  return [...body.entries()].reduce((data, [k, v]) => {
    let value: StructuredFormData = v;
    if (v === "true") value = true;
    if (v === "false") value = false;
    if (!isNaN(Number(v))) value = Number(v);

    // For grouped fields like multi-selects and checkboxes, we need to
    // store the values in an array.
    if (k in data) {
      const val = data[k];
      value = Array.isArray(val) ? [...val, value] : [val, value];
    }

    data[k] = value;

    return data;
  }, {} as Record<string, StructuredFormData>);
}
```

Now, to use this helper function

```ts
// Usage:
// src/routes/newsletter/+page.server.ts
import type { Action } from "./$types";
import { formBody } from "$lib/form-helpers";

export const POST: Action = async ({ request }) => {
  const values = await request.formData();
  const body = formBody(values);
  // Do something with the data...
  return { location: "/" };
};
```

If you used this method and then returned the data from a `+server` page, you'd see something like this:

![screenshot of response from parsing form data](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/zap1nihcpho77ets1fwx.png)

With this you can now access your form data as you're probably use to with thinks like Express.

### Client-side form submission

An additional point: this isn't the only way to submit forms in Svelte, you could also hijack the submit event and send it to an endpoint you have:

```html
<script>
  let submit

  function handleSubmit() {
    // Send a POST request to src/routes/contact/+server.ts endpoint
    submit = fetch('/contact', {
      method: 'POST',
      body: JSON.stringify({ foo: 'bar' }),
      headers: { 'content-type': 'application/json' },
    })
      .then((resp) => resp.json())
      .finally(() => setTimeout(() => (submit = null), 5000))
  }
</script>

{#if submit}
  {#await submit}
    <p>Sending...</p>
  {:then resp}
    <p>🎉 Done!</p>
    <pre>RESPONSE: {JSON.stringify(resp, null, 2)}</pre>
  {/await}
{/if}
<form on:submit|preventDefault={handleSubmit} method="post">
  <input type="text" name="email" />
  <button type="submit">Submit</button>
</form>
```

And `src/routes/contact/+server.ts` would look like:

```js
import type { RequestHandler } from "./$types";

export const POST: RequestHandler = (req) => {
  // Simulate a delay... instead you'd do something interesting here...
  await new Promise((resolve) => setTimeout(resolve, 500))

  return new Response(
    JSON.stringify({ success: true }),
    {
      status: 200, 
      headers: { 'content-type': 'application/json' }
    }
  )
}
```

---

# Fin

Thanks for reading and hope this was helpful! 🤓

This post was inspired by a question @Teunminator in Svelte's #svelte-kit Discord channel, thanks for a fun challenge!

_Follow me on [Dev.to](https://dev.to/danawoodman), [Twitter](https://twitter.com/DanaWoodman) and [Github](https://github.com/danawoodman) for more web dev and startup related content_ 🤓