{"id":19226474,"url":"https://github.com/jasonraimondi/zod-friendly-forms","last_synced_at":"2025-08-15T14:09:15.244Z","repository":{"id":65408784,"uuid":"590286151","full_name":"jasonraimondi/zod-friendly-forms","owner":"jasonraimondi","description":"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.","archived":false,"fork":false,"pushed_at":"2024-03-26T03:12:02.000Z","size":135,"stargazers_count":5,"open_issues_count":0,"forks_count":0,"subscribers_count":4,"default_branch":"main","last_synced_at":"2025-08-09T19:47:24.764Z","etag":null,"topics":["form-validation","zod","zod-lib","zod-validators"],"latest_commit_sha":null,"homepage":"https://jsr.io/@jmondi/zod-friendly-forms","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/jasonraimondi.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2023-01-18T03:43:27.000Z","updated_at":"2024-03-26T03:13:44.000Z","dependencies_parsed_at":"2024-03-26T04:26:03.021Z","dependency_job_id":"415c39e9-e227-4f9a-803d-dfdbbdc6f410","html_url":"https://github.com/jasonraimondi/zod-friendly-forms","commit_stats":{"total_commits":40,"total_committers":1,"mean_commits":40.0,"dds":0.0,"last_synced_commit":"07b490b86e5e7d09fe2e2a33e4a7b56d12efdf75"},"previous_names":["allmyfutures/zod-friendly-forms"],"tags_count":23,"template":false,"template_full_name":null,"purl":"pkg:github/jasonraimondi/zod-friendly-forms","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jasonraimondi%2Fzod-friendly-forms","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jasonraimondi%2Fzod-friendly-forms/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jasonraimondi%2Fzod-friendly-forms/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jasonraimondi%2Fzod-friendly-forms/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/jasonraimondi","download_url":"https://codeload.github.com/jasonraimondi/zod-friendly-forms/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jasonraimondi%2Fzod-friendly-forms/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":270579613,"owners_count":24610044,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-08-15T02:00:12.559Z","response_time":110,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["form-validation","zod","zod-lib","zod-validators"],"created_at":"2024-11-09T15:18:55.984Z","updated_at":"2025-08-15T14:09:15.236Z","avatar_url":"https://github.com/jasonraimondi.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Zod Friendly Forms\n\n[![JSR](https://jsr.io/badges/@jmondi/zod-friendly-forms)](https://jsr.io/@jmondi/zod-friendly-forms)\n[![JSR Score](https://jsr.io/badges/@jmondi/zod-friendly-forms/score)](https://jsr.io/@jmondi/zod-friendly-forms)\n[![GitHub Workflow Status](https://img.shields.io/github/actions/workflow/status/jasonraimondi/zod-friendly-forms/test.yml?branch=main\u0026label=Unit%20Tests\u0026style=flat-square)](https://github.com/jasonraimondi/zod-friendly-forms)\n[![Test Coverage](https://img.shields.io/codeclimate/coverage/jasonraimondi/zod-friendly-forms?style=flat-square)](https://codeclimate.com/github/jasonraimondi/zod-friendly-forms/test_coverage)\n\n**Transform Zod validation errors into user-friendly form error messages.**\n\n## The Problem\n\nZod gives you great validation, but its error messages aren't ready for end users:\n\n```ts\n// Zod's raw error output\n{\n  \"email\": [\n    {\n      \"code\": \"invalid_string\",\n      \"message\": \"Invalid email\",\n      \"path\": [\"email\"]\n    }\n  ]\n}\n```\n\n## The Solution\n\nGet clean, user-ready error messages that you can display directly in your forms:\n\n```ts\nimport { parseForm } from \"@jmondi/zod-friendly-forms\";\n\nconst { errors } = parseForm({ schema, data });\n\n// Clean, user-friendly output\n{\n  \"email\": \"Invalid email\"\n}\n```\n\n## Quick Start\n\n```ts\nimport { z } from \"zod\";\nimport { parseForm } from \"@jmondi/zod-friendly-forms\";\n\nconst schema = z.object({\n  email: z.string().email(),\n  password: z.string().min(8)\n});\n\nconst formData = {\n  email: \"invalid-email\",\n  password: \"123\"\n};\n\nconst { errors, validData } = parseForm({ schema, data: formData });\n\nif (errors) {\n  console.log(errors);\n  // {\n  //   email: \"Invalid email\",\n  //   password: \"String must contain at least 8 character(s)\"\n  // }\n} else {\n  console.log(validData); // Fully typed and validated data\n}\n```\n\n## Features\n\n- ✅ **User-friendly error messages** - Ready to display to end users\n- ✅ **Type-safe validation** - Fully typed validated data\n- ✅ **Framework agnostic** - Works with React, Vue, Svelte, vanilla JS\n- ✅ **FormData support** - Handles web forms, URLSearchParams, and plain objects\n- ✅ **Nested object errors** - Flattened dot-notation keys (e.g., `user.email`)\n- ✅ **Server \u0026 client** - Use anywhere JavaScript runs\n\n## Version Compatibility\n\n- **zod-friendly-forms v4.0+**: Supports Zod v4.x\n- **zod-friendly-forms v2.0**: Use this version for Zod v3.x compatibility\n\n## Installation\n\n### npm/pnpm\n```bash\npnpm add zod\npnpm dlx jsr add @jmondi/zod-friendly-forms\n```\n\n### Deno\n```bash\ndeno add @jmondi/zod-friendly-forms\n```\n\n## Usage\n\n### Basic Usage\n\n```ts\nimport { z } from \"zod\";\nimport { parseForm } from \"@jmondi/zod-friendly-forms\";\n\nconst schema = z.object({\n  email: z.string().email(),\n});\n\nconst { errors, validData } = parseForm({ \n  schema, \n  data: { email: \"bob@example.com\" } \n});\n\nif (!errors) {\n  // validData is fully typed as { email: string }\n  console.log(validData.email); // TypeScript knows this is a string\n}\n```\n\n### With FormData\n\nWorks seamlessly with HTML forms:\n\n```ts\nconst formData = new FormData();\nformData.append(\"email\", \"invalid-email\");\n\nconst { errors } = parseForm({ schema, data: formData });\n// { email: \"Invalid email\" }\n```\n\n### Nested Objects\n\nHandles complex nested validation with flattened error keys:\n\n```ts\nconst schema = z.object({\n  user: z.object({\n    email: z.string().email(),\n    profile: z.object({\n      name: z.string().min(2)\n    })\n  })\n});\n\nconst { errors } = parseForm({ \n  schema, \n  data: { \n    user: { \n      email: \"invalid\", \n      profile: { name: \"x\" } \n    } \n  } \n});\n\nconsole.log(errors);\n// {\n//   \"user.email\": \"Invalid email\",\n//   \"user.profile.name\": \"String must contain at least 2 character(s)\"\n// }\n```\n\n### Options\n\n```ts\nconst { errors, validData } = parseForm(\n  { schema, data },\n  { stripEmptyStrings: true } // Convert empty strings to undefined\n);\n```\n\n## Framework Examples\n\n### React\n\n```tsx\nimport { useState } from \"react\";\nimport { z } from \"zod\";\nimport { parseForm } from \"@jmondi/zod-friendly-forms\";\n\nconst LoginSchema = z.object({\n  email: z.string().email(),\n  password: z.string().min(8),\n});\n\nexport function LoginForm() {\n  const [formData, setFormData] = useState({ email: \"\", password: \"\" });\n  const [errors, setErrors] = useState({});\n\n  const handleSubmit = async (e) =\u003e {\n    e.preventDefault();\n    const { errors, validData } = parseForm({ \n      schema: LoginSchema, \n      data: formData \n    });\n    \n    if (errors) {\n      setErrors(errors);\n    } else {\n      // Handle successful login with validData\n      await login(validData);\n    }\n  };\n\n  return (\n    \u003cform onSubmit={handleSubmit}\u003e\n      \u003cinput \n        type=\"email\" \n        value={formData.email}\n        onChange={(e) =\u003e setFormData({...formData, email: e.target.value})}\n      /\u003e\n      {errors.email \u0026\u0026 \u003cspan className=\"error\"\u003e{errors.email}\u003c/span\u003e}\n      \n      \u003cinput \n        type=\"password\" \n        value={formData.password}\n        onChange={(e) =\u003e setFormData({...formData, password: e.target.value})}\n      /\u003e\n      {errors.password \u0026\u0026 \u003cspan className=\"error\"\u003e{errors.password}\u003c/span\u003e}\n      \n      \u003cbutton type=\"submit\"\u003eLogin\u003c/button\u003e\n    \u003c/form\u003e\n  );\n}\n```\n\n### Svelte\n\n```svelte\n\u003cscript lang=\"ts\"\u003e\n  import { z } from \"zod\";\n  import { parseForm } from \"@jmondi/zod-friendly-forms\";\n\n  const schema = z.object({\n    email: z.string().email(),\n    password: z.string().min(8),\n  });\n\n  let formData = { email: \"\", password: \"\" };\n  let errors = {};\n\n  async function handleSubmit() {\n    const result = parseForm({ schema, data: formData });\n    if (result.errors) {\n      errors = result.errors;\n    } else {\n      await login(result.validData);\n    }\n  }\n\u003c/script\u003e\n\n\u003cform on:submit|preventDefault={handleSubmit}\u003e\n  \u003cinput bind:value={formData.email} type=\"email\" /\u003e\n  {#if errors.email}\u003cspan class=\"error\"\u003e{errors.email}\u003c/span\u003e{/if}\n  \n  \u003cinput bind:value={formData.password} type=\"password\" /\u003e\n  {#if errors.password}\u003cspan class=\"error\"\u003e{errors.password}\u003c/span\u003e{/if}\n  \n  \u003cbutton type=\"submit\"\u003eLogin\u003c/button\u003e\n\u003c/form\u003e\n```\n\n### Vue\n\n```vue\n\u003ctemplate\u003e\n  \u003cform @submit.prevent=\"handleSubmit\"\u003e\n    \u003cinput v-model=\"formData.email\" type=\"email\" /\u003e\n    \u003cspan v-if=\"errors.email\" class=\"error\"\u003e{{ errors.email }}\u003c/span\u003e\n    \n    \u003cinput v-model=\"formData.password\" type=\"password\" /\u003e\n    \u003cspan v-if=\"errors.password\" class=\"error\"\u003e{{ errors.password }}\u003c/span\u003e\n    \n    \u003cbutton type=\"submit\"\u003eLogin\u003c/button\u003e\n  \u003c/form\u003e\n\u003c/template\u003e\n\n\u003cscript\u003e\nimport { z } from 'zod';\nimport { parseForm } from '@jmondi/zod-friendly-forms';\n\nconst schema = z.object({\n  email: z.string().email(),\n  password: z.string().min(8),\n});\n\nexport default {\n  data() {\n    return {\n      formData: { email: \"\", password: \"\" },\n      errors: {}\n    };\n  },\n  methods: {\n    async handleSubmit() {\n      const { errors, validData } = parseForm({ \n        schema, \n        data: this.formData \n      });\n      \n      if (errors) {\n        this.errors = errors;\n      } else {\n        await this.login(validData);\n      }\n    }\n  }\n};\n\u003c/script\u003e\n```\n\n## API Reference\n\n### `parseForm(params, options?)`\n\n**Parameters:**\n- `params.schema` - Zod schema for validation\n- `params.data` - Form data (object, FormData, or URLSearchParams)\n- `options.stripEmptyStrings` - Convert empty strings to undefined (optional)\n\n**Returns:**\n- Success: `{ validData: T, errors: undefined }`\n- Failure: `{ errors: Record\u003cstring, string\u003e, validData: undefined }`\n\n## License\n\nMIT","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjasonraimondi%2Fzod-friendly-forms","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fjasonraimondi%2Fzod-friendly-forms","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjasonraimondi%2Fzod-friendly-forms/lists"}