DEV Community

Dana Woodman
Dana Woodman

Posted on • Edited on

Getting form body data in SvelteKit actions/server pages

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 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:

<!-- 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>
Enter fullscreen mode Exit fullscreen mode

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 to get our data.

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

// 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: "/" };
};
Enter fullscreen mode Exit fullscreen mode

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:

// 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>);
}
Enter fullscreen mode Exit fullscreen mode

Now, to use this helper function

// 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: "/" };
};
Enter fullscreen mode Exit fullscreen mode

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

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:

<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>
Enter fullscreen mode Exit fullscreen mode

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

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' }
    }
  )
}
Enter fullscreen mode Exit fullscreen mode

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, Twitter and Github for more web dev and startup related content 🤓

Top comments (8)

Collapse
 
myleftshoe profile image
myleftshoe • Edited

This seems to be the latest way to get form data:

export const post = async ({request}) => {
    const data = await request.formData()
    console.table([...data.entries()])
    return {
        headers: { Location: '/' },
        status: 302
    }
}

Enter fullscreen mode Exit fullscreen mode
Collapse
 
xenogew profile image
Natta Wang

if TypeScript, how could I specify type of 'request'?

Collapse
 
myleftshoe profile image
myleftshoe

Don't know tbh, I don't use type script. Since Svelte has excellent typescript support it must be in the docs somewhere. :(

Thread Thread
 
xenogew profile image
Natta Wang • Edited

Thanks for your reply, it's ok, I got answer.

For those who might want to know, I'll leave it here

post({ request }: RequestEvent) {

Reason: it's destructure pattern, we need to specify type of object that will be passed to post function, and here we are looking for request property which is property of RequestEvent.

PS. RequestEvent is a type of Svelte Kit library.

Collapse
 
weepy profile image
weepy • Edited

Can you just do :

const data = await request.formData()
const { username, ...} = Object.fromEntries(data)
Enter fullscreen mode Exit fullscreen mode
Collapse
 
manuganji profile image
Manu Ganji

Hi, thank you for writing about this. It helped me but this was changed in this pull request.

github.com/sveltejs/kit/pull/3384

Collapse
 
yawnxyz profile image
Jan Z

yeah I kind of wish there was a flag you could switch in config to use the request object – it's so convenient!

Collapse
 
alphonso06 profile image
Al Javier

Is there a way to do this in JSDoc? Haven't been able to come up with a good JSDoc version yet.