Skip to content

How Do SvelteKit Form Actions Work? Server-Side Form Handling Made Simple

Form validation web development

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:

  1. Serialize form data
  2. Make POST request with fetch
  3. Handle response
  4. Update UI state
  5. Manage loading states
  6. Handle errors

This creates unnecessary boilerplate.

The SvelteKit Solution

Forms work without JavaScript by default:

+page.server.js
export const actions = {
default: async (event) => {
// Handle form submission on server
}
};
+page.svelte
<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:

+page.server.js
export const actions = {
login: async (event) => {
// Handle login
},
register: async (event) => {
// Handle registration
}
};
+page.svelte
<form method="POST" action="?/login">
<button>Log in</button>
<button formaction="?/register">Register</button>
</form>

Validation and Error Handling

+page.server.js
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

+page.server.js
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');
}
};
+page.svelte
<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:

+page.svelte
<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