https://github.com/jasonraimondi/zod-friendly-forms
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.
https://github.com/jasonraimondi/zod-friendly-forms
form-validation zod zod-lib zod-validators
Last synced: 10 months ago
JSON representation
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.
- Host: GitHub
- URL: https://github.com/jasonraimondi/zod-friendly-forms
- Owner: jasonraimondi
- License: mit
- Created: 2023-01-18T03:43:27.000Z (over 3 years ago)
- Default Branch: main
- Last Pushed: 2024-03-26T03:12:02.000Z (about 2 years ago)
- Last Synced: 2025-08-09T19:47:24.764Z (10 months ago)
- Topics: form-validation, zod, zod-lib, zod-validators
- Language: TypeScript
- Homepage: https://jsr.io/@jmondi/zod-friendly-forms
- Size: 132 KB
- Stars: 5
- Watchers: 4
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- License: LICENSE
Awesome Lists containing this project
README
# Zod Friendly Forms
[](https://jsr.io/@jmondi/zod-friendly-forms)
[](https://jsr.io/@jmondi/zod-friendly-forms)
[](https://github.com/jasonraimondi/zod-friendly-forms)
[](https://codeclimate.com/github/jasonraimondi/zod-friendly-forms/test_coverage)
**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:
```ts
// 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:
```ts
import { parseForm } from "@jmondi/zod-friendly-forms";
const { errors } = parseForm({ schema, data });
// Clean, user-friendly output
{
"email": "Invalid email"
}
```
## Quick Start
```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)
});
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
```bash
pnpm add zod
pnpm dlx jsr add @jmondi/zod-friendly-forms
```
### Deno
```bash
deno add @jmondi/zod-friendly-forms
```
## Usage
### Basic Usage
```ts
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:
```ts
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:
```ts
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
```ts
const { errors, validData } = parseForm(
{ schema, data },
{ stripEmptyStrings: true } // Convert empty strings to undefined
);
```
## Framework Examples
### React
```tsx
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 (
setFormData({...formData, email: e.target.value})}
/>
{errors.email && {errors.email}}
setFormData({...formData, password: e.target.value})}
/>
{errors.password && {errors.password}}
Login
);
}
```
### Svelte
```svelte
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);
}
}
{#if errors.email}{errors.email}{/if}
{#if errors.password}{errors.password}{/if}
Login
```
### Vue
```vue
{{ errors.email }}
{{ errors.password }}
Login
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);
}
}
}
};
```
## API Reference
### `parseForm(params, options?)`
**Parameters:**
- `params.schema` - Zod schema for validation
- `params.data` - Form data (object, FormData, or URLSearchParams)
- `options.stripEmptyStrings` - Convert empty strings to undefined (optional)
**Returns:**
- Success: `{ validData: T, errors: undefined }`
- Failure: `{ errors: Record, validData: undefined }`
## License
MIT