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>
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: "/" };
};
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>);
}
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: "/" };
};
If you used this method and then returned the data from a +server
page, you'd see something like this:
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>
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' }
}
)
}
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)
This seems to be the latest way to get form data:
if TypeScript, how could I specify type of 'request'?
Don't know tbh, I don't use type script. Since Svelte has excellent typescript support it must be in the docs somewhere. :(
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 forrequest
property which is property ofRequestEvent
.PS. RequestEvent is a type of Svelte Kit library.
Can you just do :
Hi, thank you for writing about this. It helped me but this was changed in this pull request.
github.com/sveltejs/kit/pull/3384
yeah I kind of wish there was a flag you could switch in config to use the
request
object – it's so convenient!Is there a way to do this in JSDoc? Haven't been able to come up with a good JSDoc version yet.