A simple and powerful form handling library for SvelteKit remote forms with Zod schema validation built in.
- Simple API - Minimal boilerplate with maximum functionality
- Type-safe - Full TypeScript support with Zod schema validation
- SvelteKit integration - Built specifically for SvelteKit's form actions
- Automatic error handling - Easy access to field and form errors
- Flexible hooks - Custom success, error, and settled callbacks
- Form utilities - Built-in form reset and data appending
npm install svelte-ez-form
pnpm add svelte-ez-form
yarn add svelte-ez-form
This library requires:
- Svelte 5.0+
- SvelteKit with remote functions enabled
- Zod for schema validation (peer dependency)
// src/routes/example.remote.ts
import { form } from '$app/server';
import { ezValidate } from 'svelte-ez-form';
import { z } from 'zod';
const schema = z.object({
name: z.string().min(1, 'Name must be present')
});
export const exampleForm = form(async (data) => {
return await ezValidate(schema, data, {
onSuccess: (validated) => {
console.log('Validation successful:', validated);
// Perform additional server-side actions on success
// ex: query.refresh()
return { message: 'User created successfully' };
},
onError: (errors) => {
console.log('Validation failed:', errors);
// Handle validation errors on the server
return { errorCode: 'VALIDATION_FAILED' };
}
});
});
<!-- src/routes/+page.svelte -->
<script lang="ts">
import { ezForm, ezErrors } from 'svelte-ez-form';
import { exampleForm } from './example.remote.js';
let someObject = {
name: 'John Doe',
email: ''
};
const form = ezForm(exampleForm, {
onSuccess: async (result) => {
console.log('Form submitted successfully:', result);
},
onError: (error) => {
console.error('Form submission error:', error);
},
append: {
...someObject // Append additional data to form submission
}
});
let { errors } = $derived(ezErrors(exampleForm));
</script>
<form {...form}>
<input type="text" name="name" placeholder="Name" />
{#if errors?.name}
<span class="error">{errors.name}</span>
{/if}
</form>
Validates form data against a Zod schema with optional server-side hooks.
Parameters:
schema
- A Zod schema to validate againstformData
- The FormData object from the requestoptions
- Optional configuration object for server-side hooks
Options:
type EZValidateOptions<TSchema, SExtra = undefined, EExtra = undefined> = {
onSuccess?: (data: z.output<TSchema>) => SExtra | Promise<SExtra>;
onError?: (errors: FieldErrors<TSchema>) => EExtra | Promise<EExtra>;
onSettled?: (result: EZSuccess<TSchema, SExtra> | EZFailure<TSchema, EExtra>) => void | Promise<void>;
};
Returns:
- Promise that resolves to:
- Success:
{ success: true, data: T, returns: SExtra }
- Error:
{ success: false, errors: FieldErrors<T>, formErrors: string[], returns: EExtra }
- Success:
Example with hooks:
const result = await ezValidate(schema, formData, {
onSuccess: async (data) => {
// Save to database, send emails, etc.
await saveUserToDatabase(data);
await sendWelcomeEmail(data.email);
return { userId: data.id, message: 'Welcome!' };
},
onError: async (errors) => {
// Log validation errors, analytics, etc.
await logValidationErrors(errors);
return { errorCode: 'VALIDATION_FAILED' };
},
onSettled: async (result) => {
// Always runs after success or error
console.log('Form processing completed:', result);
}
});
if (result.success) {
console.log('User ID:', result.returns.userId);
console.log('Message:', result.returns.message);
} else {
console.log('Error code:', result.returns.errorCode);
}
Enhances SvelteKit's form handling with additional functionality.
Parameters:
form
- The SvelteKit RemoteForm objectoptions
- Optional configuration object
Options:
type EZFormOptions<T> = {
onSuccess?: (result: ValidationResult<T>) => void | Promise<void>;
onError?: (result: ValidationResult<T>) => void | Promise<void>;
onSettled?: (result: ValidationResult<T> | undefined) => void | Promise<void>;
append?: Record<string, FormDataEntryValue>;
reset?: {
onError?: boolean;
onSuccess?: boolean;
};
};
Extracts errors from the form result for easy access in templates.
Parameters:
form
- The SvelteKit RemoteForm object
Returns:
{
errors: Record<string, string[]> | undefined;
formErrors: string[] | undefined;
}
The ezValidate
function now supports server-side hooks that execute during validation. These hooks allow you to perform additional server-side operations when validation succeeds or fails.
The onSuccess
hook executes when validation passes:
export const createUserForm = form(async (data) => {
return await ezValidate(userSchema, data, {
onSuccess: async (validatedData) => {
// Save to database
const user = await db.users.create(validatedData);
// Send welcome email
await emailService.sendWelcome(user.email);
// Log successful registration
console.log(`User ${user.id} created successfully`);
// Return data that will be available in result.returns
return { userId: user.id, welcomeEmailSent: true };
}
});
});
The onError
hook executes when validation fails:
export const loginForm = form(async (data) => {
return await ezValidate(loginSchema, data, {
onError: async (errors) => {
// Log failed validation attempts
await auditLog.logFailedValidation({
timestamp: new Date(),
errors,
ip: request.ip
});
// Track analytics
analytics.track('form_validation_failed', { errors });
// Return error context
return { attemptId: crypto.randomUUID(), blocked: false };
}
});
});
export const updateProfileForm = form(async (data) => {
return await ezValidate(profileSchema, data, {
onSuccess: async (validatedData) => {
await db.profiles.update(userId, validatedData);
await cacheService.invalidateUser(userId);
return { updated: true, timestamp: new Date() };
},
onError: async (errors) => {
await logger.warn('Profile update validation failed', { userId, errors });
return { errorId: crypto.randomUUID() };
},
onSettled: async (result) => {
// Always log the attempt regardless of success/failure
await auditLog.profileUpdateAttempt({
userId,
success: result.success,
timestamp: new Date()
});
}
});
});
The append
option allows you to automatically add extra data to your form submissions without manually adding hidden inputs. This is particularly useful for:
- Adding user context (user ID, session data)
- Including timestamps
- Appending metadata or configuration
- Adding authentication tokens
- Reactive variables like
$state()
and$derived()
must be retrieved using a closure
let userContext = {
userId: '12345',
timestamp: new Date().toISOString()
};
let userName = $derived(users[userContext['userId']])
const form = ezForm(exampleForm, {
append: {
...userContext,
action: 'create',
// since userName is a reactive variable we have to use a closure so we get the most up to date value when the form is submitted.
userName: () => userName
}
});
let someObject = {
name: 'John Doe',
email: 'john@example.com'
};
const form = ezForm(exampleForm, {
append: {
...someObject, // Spread existing object
submittedAt: new Date().toISOString(),
browserInfo: navigator.userAgent
}
});
The appended data will be available in your form validation alongside the regular form fields:
// In your remote form
const schema = z.object({
name: z.string().min(1, 'Name required'),
// These will be automatically appended
userId: z.string().optional(),
timestamp: z.string().optional(),
action: z.string().optional()
});
<script lang="ts">
import { ezForm } from 'svelte-ez-form';
import { goto } from '$app/navigation';
import { toast } from '$lib/toast';
let someReactiveState = $state("")
const enhanceForm = ezForm(form, {
onSuccess: async (result) => {
toast.success('Profile updated successfully!');
await goto('/dashboard');
},
onError: (result) => {
toast.error('Please fix the errors and try again');
},
append: {
timestamp: new Date().toISOString(),
append: {
someReactiveState: () => someReactiveState
}
},
reset: {
onSuccess: true
}
});
</script>
svelte-ez-form is built with TypeScript and provides full type safety:
import { z } from 'zod';
import type { ValidationResult } from 'svelte-ez-form';
const userSchema = z.object({
name: z.string(),
email: z.string().email()
});
type User = z.infer<typeof userSchema>;
// Type-safe async validation result
const result = await ezValidate(userSchema, formData, {
onSuccess: (data: User) => {
// data is fully typed as User
console.log(`Welcome ${data.name}!`);
return { welcomeMessage: `Hello ${data.name}!` };
},
onError: (errors: FieldErrors<typeof userSchema>) => {
// errors are typed based on the schema
console.log('Validation errors:', errors);
return { errorCount: Object.keys(errors).length };
}
});
if (result.success) {
// result.data is typed as User
console.log(result.data.name, result.data.email);
// result.returns is typed based on onSuccess return type
console.log(result.returns.welcomeMessage);
} else {
// result.returns is typed based on onError return type
console.log(`Found ${result.returns.errorCount} errors`);
}
Based on your actual usage pattern:
{#if errors?.name}
<span class="error">{errors.name}</span>
{/if}
{#if errors?.fieldName}
<span class="error">{errors.fieldName[0]}</span>
{/if}
{#if errors?.fieldName}
{#each errors.fieldName as error}
<span class="error">{error}</span>
{/each}
{/if}
{#if formErrors}
<div class="form-errors">
{#each formErrors as error}
<div class="error">{error}</div>
{/each}
</div>
{/if}
Contributions are welcome! Please feel free to submit a Pull Request.
MIT
- Updated
ezErrors
to accept new return types correctly.
- Added typesafe returns for the
returns
provided back toezForm
- BREAKING: Updated
ezValidate
return types with newEZSuccess
andEZFailure
structures - BREAKING: Hook functions (
onSuccess
,onError
) can now return values that are captured inresult.returns
- Added
onSettled
hook that runs after both success and error scenarios - Enhanced type safety with generic return types for hook functions
- Improved error handling with try-catch blocks around hook execution
- Link GH repo
- Link GH
- Allow passing reactive variables to append via closures
- Validated results will now automatically parse JSON data
- BREAKING:
ezValidate
is now async and returns aPromise<ValidationResult<T>>
- Added server-side hooks:
onSuccess
andonError
options forezValidate
- Enhanced TypeScript support for async validation
- Improved server-side integration with SvelteKit remote forms
- Initial release
- Basic form validation with Zod
- Error handling utilities
- TypeScript support