Skip to main content

Form UX Pack

Available in: Starter, Professional, Business, Enterprise tiers

Schema-driven React forms with automatic validation. Generate fully-typed form components from your Prisma schema using React Hook Form and Zod.

Why Use Form UX Pack

Problem: Building forms is repetitive and error-prone:

  • Manually creating form components for each model
  • Writing validation logic that duplicates schema rules
  • Keeping forms in sync with database schema changes
  • Handling complex nested relationships
  • Managing form state, errors, and submissions

Solution: Auto-generate type-safe form components from your Prisma schema with built-in validation, error handling, and customizable UI.

Benefits

  • Zero Boilerplate: Forms generated from schema
  • Type Safety: Full TypeScript from database to UI
  • Auto Validation: Zod schemas automatically applied
  • Five UI libraries: barebones, shadcn/ui, MUI, Chakra v3 and Mantine, or swap in your own
  • Nested Relations: Handle complex data structures
  • Form State: Built-in loading, error, and success states

Prerequisites

# Core dependencies
pnpm add react react-dom react-hook-form @hookform/resolvers zod @prisma/client

# UI library (optional - example with shadcn/ui)
pnpm add class-variance-authority clsx tailwind-merge
npx shadcn@latest init

# PZG Pro license required

Generate

Add to your schema.prisma:

generator pzgPro {
provider = "node ./node_modules/prisma-zod-generator/lib/cli/pzg-pro.js"
output = "./generated/pro"
enableForms = true

// Optional advanced config (stringified JSON)
// forms = "{ \"uiLibrary\": \"shadcn\", \"enableI18n\": true, \"i18nNamespace\": \"forms\", \"generateTests\": true }"
}

Pass additional options (UI library, i18n, tests, etc.) through the forms JSON string. All keys must be valid JSON and wrapped in quotes inside the Prisma schema.

Then run:

prisma generate

Note: uiLibrary accepts barebones and shadcn. Use barebones for framework-agnostic forms built from plain elements, or shadcn for components wired to shadcn/ui primitives (Input, Textarea, Checkbox, Select).

All five values are implemented from 2.4.3+: barebones, shadcn, mui, chakra and mantine. An unrecognised value falls back to barebones and says so. (Between 2.4.1 and 2.4.2 mui/chakra/mantine fell back to barebones; before 2.4.1 they emitted shadcn markup with no imports, which could not compile.)

Install the peer dependencies for whichever you choose:

# mui
pnpm add @mui/material @emotion/react @emotion/styled
# chakra (v3)
pnpm add @chakra-ui/react
# mantine
pnpm add @mantine/core

mui, chakra and mantine render through react-hook-form's Controller, because their inputs are controlled — register() alone does not propagate a Select or Checkbox change. Enum columns become a select populated with the enum's members in every variant.

Chakra targets v3

The emitted components use the v3 API — Field.Root/Field.Label/Field.ErrorText, NativeSelect, compound Checkbox, and disabled rather than isDisabled. They do not compile against Chakra v2, whose FormControl/FormLabel/FormErrorMessage were removed in v3.

Generated Files

generated/
pro/
forms/
components/
UserForm.tsx # User form component
PostForm.tsx # Post form component
validation/
UserValidation.ts # User validation helpers
PostValidation.ts # Post validation helpers
i18n/ # i18n translation keys (if enabled)
user.json
post.json
__tests__/ # Form tests (if enabled)
UserForm.test.tsx
PostForm.test.tsx
zod.ts # Zod schemas for all models
index.ts # Exports all forms and validation
README.md # Usage documentation

Basic Usage

// app/users/create/page.tsx
import { UserForm } from '@/generated/pro/forms'

export default function CreateUserPage() {
return (
<UserForm
defaultValues={{
email: '',
name: ''
}}
onSubmit={async (data) => {
// Send to API
const response = await fetch('/api/users', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data)
})

if (response.ok) {
console.log('User created')
}
}}
/>
)
}

Edit Form Example

// app/users/[id]/edit/page.tsx
import { UserForm } from '@/generated/pro/forms'

export default async function EditUserPage({ params }: { params: { id: string } }) {
const user = await fetch(`/api/users/${params.id}`).then(r => r.json())

return (
<UserForm
defaultValues={user}
onSubmit={async (data) => {
await fetch(`/api/users/${user.id}`, {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data)
})
}}
/>
)
}

Integration Notes

Validation & Controllers

Generated forms rely on react-hook-form with Zod validation. For shadcn output, the generator uses Controller with shadcn primitives (Input, Textarea, Checkbox, and Select) and avoids the deprecated @/components/ui/form abstraction. If you prefer manual register() calls (e.g., for simple text inputs), you can still wire up useForm directly—just make sure to use the same Zod resolver.

Schema Imports

The generated forms import validation schemas from your Prisma Zod output. Prefer importing from a generated aggregator (e.g., prisma/zod/index.ts) that maps long object names to short forms like UserCreateInputSchema.

Nested Relations

If a Prisma create schema includes nested relations, provide a compatible default (e.g., {}) or render nested form fields to handle the relationship data.

Customizing UI

The generated form components are designed to work with your UI library of choice. You can:

  • Swap out the default input components
  • Add custom styling and classes
  • Integrate with component libraries like shadcn/ui, MUI, Chakra, etc.
  • Customize error display and form layout

Example: shadcn/ui Integration

// Example using RHF register + shadcn/ui inputs
import { useForm } from 'react-hook-form'
import { zodResolver } from '@hookform/resolvers/zod'
import { z } from 'zod'
import { UserSchema } from '@/generated/pro/forms/zod'

import { Input } from '@/components/ui/input'
import { Label } from '@/components/ui/label'
import { Button } from '@/components/ui/button'

type UserFormValues = z.infer<typeof UserSchema>

export function UserForm({ defaultValues, onSubmit }: UserFormProps) {
const form = useForm<UserFormValues>({
resolver: zodResolver(UserSchema),
defaultValues,
})

return (
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-6">
<div className="space-y-2">
<Label htmlFor="email">Email</Label>
<Input id="email" type="email" {...form.register('email')} />
{form.formState.errors.email && (
<p className="text-sm text-red-500">
{form.formState.errors.email.message}
</p>
)}
</div>

<Button type="submit" disabled={form.formState.isSubmitting}>
Submit
</Button>
</form>
)
}

See Also