How Do SvelteKit Form Actions Work? Server-Side Form Handling Made Simple
Problem
I was tired of writing fetch calls everywhere for form handling. From Reddit:
“the one thing that surprised me was how good the form actions are. server-side form handling without writing any client JS feels weirdly refreshing after years of fetch calls everywhere.”
This captures the problem perfectly - we’ve normalized complexity that the web platform already solved.
Environment
- SvelteKit with form actions
- Standard HTML forms
- Progressive enhancement with
use:enhance
What happened?
The Traditional Approach
With traditional SPA development, form handling requires:
- Serialize form data
- Make POST request with fetch
- Handle response
- Update UI state
- Manage loading states
- Handle errors
This creates unnecessary boilerplate.
The SvelteKit Solution
Forms work without JavaScript by default:
export const actions = { default: async (event) => { // Handle form submission on server }};<form method="POST"> <label> Email <input name="email" type="email"> </label> <label> Password <input name="password" type="password"> </label> <button>Log in</button></form>No JavaScript required. The browser handles submission natively.
Named Actions
Multiple actions per page:
export const actions = { login: async (event) => { // Handle login }, register: async (event) => { // Handle registration }};<form method="POST" action="?/login"> <button>Log in</button> <button formaction="?/register">Register</button></form>Validation and Error Handling
import { fail } from '@sveltejs/kit';
export const actions = { login: async ({ request }) => { const data = await request.formData(); const email = data.get('email');
if (!email) { return fail(400, { email, missing: true }); }
return { success: true }; }};How to use it?
Complete Login Example
import { fail, redirect } from '@sveltejs/kit';
export const actions = { login: async ({ cookies, request }) => { const data = await request.formData(); const email = data.get('email'); const password = data.get('password');
if (!email) { return fail(400, { email, missing: true }); }
// Validate credentials const user = await getUser(email); if (!user || !validPassword(password, user)) { return fail(400, { email, incorrect: true }); }
cookies.set('sessionid', createSession(user), { path: '/', httpOnly: true, secure: true, sameSite: 'lax' });
throw redirect(303, '/dashboard'); }};<script> import { enhance } from '$app/forms'; let { data, form } = $props();</script>
<form method="POST" action="?/login" use:enhance> {#if form?.missing} <p class="error">Email is required</p> {/if}
{#if form?.incorrect} <p class="error">Invalid credentials</p> {/if}
<label> Email <input name="email" type="email" value={form?.email ?? ''}> </label>
<label> Password <input name="password" type="password"> </label>
<button>Log in</button></form>Progressive Enhancement
Add use:enhance for better UX when JavaScript is available:
<script> import { enhance } from '$app/forms';</script>
<form method="POST" use:enhance> <!-- form fields --></form>The reason
SvelteKit form actions return to web fundamentals. Forms work without JavaScript, providing:
- Better accessibility
- Resilience to JavaScript errors
- Improved SEO
- Faster perceived performance
Summary
In this post, I explained how SvelteKit form actions work. The key point is server-side form handling eliminates fetch boilerplate, and forms work without JavaScript by default with optional progressive enhancement via use:enhance.
Final Words + More Resources
My intention with this article was to help others share my knowledge and experience. If you want to contact me, you can contact by email: Email me
Here are also the most important links from this article along with some further resources that will help you in this scope:
Oh, and if you found these resources useful, don’t forget to support me by starring the repo on GitHub!
Comments