Validate forms with ease using Zod and get user-friendly error messages or valid typed data, compatible with any framework on both server and client side.
Attributes
Includes Deno configuration
Repository
Current version released
11 months ago
Dependencies
other
Versions
- v4.0.0Latest
- v1.4.0
- v1.4.0
- v1.3.0
- v1.2.0
- v1.1.0
- v1.1.0
- v1.1.0-rc.1
- v1.0.1
- v1.0.0
- v1.0.0-alpha.1
- 0.3.0-rc.3
- 0.3.0-rc.3
- 0.3.0-rc.2
- v0.3.0-rc.1
- v0.3.0-alpha.16
- v0.3.0-alpha.15
- v0.3.0-alpha.9
- v0.3.0-alpha.8
- v0.3.0-alpha.7
- v0.3.0-alpha.6
- v0.3.0-alpha.5
- v0.3.0-alpha.4
- v0.3.0-alpha.3
- v0.3.0-alpha.2
- v0.3.0-alpha.1
- v0.2.1
- v0.2.0
- v0.2.0
Zod Friendly Forms
Transform Zod validation errors into user-friendly form error messages.
The Problem
Zod gives you great validation, but its error messages aren’t ready for end users:
// Zod's raw error output
{
"email": [
{
"code": "invalid_string",
"message": "Invalid email",
"path": ["email"]
}
]
}The Solution
Get clean, user-ready error messages that you can display directly in your forms:
import { parseForm } from "@jmondi/zod-friendly-forms";
const { errors } = parseForm({ schema, data });
// Clean, user-friendly output
{
"email": "Invalid email"
}Quick Start
import { z } from "zod";
import { parseForm } from "@jmondi/zod-friendly-forms";
const schema = z.object({
email: z.string().email(),
password: z.string().min(8)
});
const formData = {
email: "invalid-email",
password: "123"
};
const { errors, validData } = parseForm({ schema, data: formData });
if (errors) {
console.log(errors);
// {
// email: "Invalid email",
// password: "String must contain at least 8 character(s)"
// }
} else {
console.log(validData); // Fully typed and validated data
}Features
- ✅ User-friendly error messages - Ready to display to end users
- ✅ Type-safe validation - Fully typed validated data
- ✅ Framework agnostic - Works with React, Vue, Svelte, vanilla JS
- ✅ FormData support - Handles web forms, URLSearchParams, and plain objects
- ✅ Nested object errors - Flattened dot-notation keys (e.g.,
user.email) - ✅ Server & client - Use anywhere JavaScript runs
Version Compatibility
- zod-friendly-forms v4.0+: Supports Zod v4.x
- zod-friendly-forms v2.0: Use this version for Zod v3.x compatibility
Installation
npm/pnpm
pnpm add zod
pnpm dlx jsr add @jmondi/zod-friendly-formsDeno
deno add @jmondi/zod-friendly-formsUsage
Basic Usage
import { z } from "zod";
import { parseForm } from "@jmondi/zod-friendly-forms";
const schema = z.object({
email: z.string().email(),
});
const { errors, validData } = parseForm({
schema,
data: { email: "bob@example.com" }
});
if (!errors) {
// validData is fully typed as { email: string }
console.log(validData.email); // TypeScript knows this is a string
}With FormData
Works seamlessly with HTML forms:
const formData = new FormData();
formData.append("email", "invalid-email");
const { errors } = parseForm({ schema, data: formData });
// { email: "Invalid email" }Nested Objects
Handles complex nested validation with flattened error keys:
const schema = z.object({
user: z.object({
email: z.string().email(),
profile: z.object({
name: z.string().min(2)
})
})
});
const { errors } = parseForm({
schema,
data: {
user: {
email: "invalid",
profile: { name: "x" }
}
}
});
console.log(errors);
// {
// "user.email": "Invalid email",
// "user.profile.name": "String must contain at least 2 character(s)"
// }Options
const { errors, validData } = parseForm(
{ schema, data },
{ stripEmptyStrings: true } // Convert empty strings to undefined
);Framework Examples
React
import { useState } from "react";
import { z } from "zod";
import { parseForm } from "@jmondi/zod-friendly-forms";
const LoginSchema = z.object({
email: z.string().email(),
password: z.string().min(8),
});
export function LoginForm() {
const [formData, setFormData] = useState({ email: "", password: "" });
const [errors, setErrors] = useState({});
const handleSubmit = async (e) => {
e.preventDefault();
const { errors, validData } = parseForm({
schema: LoginSchema,
data: formData
});
if (errors) {
setErrors(errors);
} else {
// Handle successful login with validData
await login(validData);
}
};
return (
<form onSubmit={handleSubmit}>
<input
type="email"
value={formData.email}
onChange={(e) => setFormData({...formData, email: e.target.value})}
/>
{errors.email && <span className="error">{errors.email}</span>}
<input
type="password"
value={formData.password}
onChange={(e) => setFormData({...formData, password: e.target.value})}
/>
{errors.password && <span className="error">{errors.password}</span>}
<button type="submit">Login</button>
</form>
);
}Svelte
<script lang="ts">
import { z } from "zod";
import { parseForm } from "@jmondi/zod-friendly-forms";
const schema = z.object({
email: z.string().email(),
password: z.string().min(8),
});
let formData = { email: "", password: "" };
let errors = {};
async function handleSubmit() {
const result = parseForm({ schema, data: formData });
if (result.errors) {
errors = result.errors;
} else {
await login(result.validData);
}
}
</script>
<form on:submit|preventDefault={handleSubmit}>
<input bind:value={formData.email} type="email" />
{#if errors.email}<span class="error">{errors.email}</span>{/if}
<input bind:value={formData.password} type="password" />
{#if errors.password}<span class="error">{errors.password}</span>{/if}
<button type="submit">Login</button>
</form>Vue
<template>
<form @submit.prevent="handleSubmit">
<input v-model="formData.email" type="email" />
<span v-if="errors.email" class="error">{{ errors.email }}</span>
<input v-model="formData.password" type="password" />
<span v-if="errors.password" class="error">{{ errors.password }}</span>
<button type="submit">Login</button>
</form>
</template>
<script>
import { z } from 'zod';
import { parseForm } from '@jmondi/zod-friendly-forms';
const schema = z.object({
email: z.string().email(),
password: z.string().min(8),
});
export default {
data() {
return {
formData: { email: "", password: "" },
errors: {}
};
},
methods: {
async handleSubmit() {
const { errors, validData } = parseForm({
schema,
data: this.formData
});
if (errors) {
this.errors = errors;
} else {
await this.login(validData);
}
}
}
};
</script>API Reference
parseForm(params, options?)
Parameters:
params.schema- Zod schema for validationparams.data- Form data (object, FormData, or URLSearchParams)options.stripEmptyStrings- Convert empty strings to undefined (optional)
Returns:
- Success:
{ validData: T, errors: undefined } - Failure:
{ errors: Record<string, string>, validData: undefined }
License
MIT