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
formsJSON string. All keys must be valid JSON and wrapped in quotes inside the Prisma schema.
Then run:
prisma generate
Note:
uiLibraryacceptsbarebonesandshadcn. Usebarebonesfor framework-agnostic forms built from plain elements, orshadcnfor components wired to shadcn/ui primitives (Input,Textarea,Checkbox,Select).All five values are implemented from 2.4.3+:
barebones,shadcn,mui,chakraandmantine. An unrecognised value falls back tobarebonesand says so. (Between 2.4.1 and 2.4.2mui/chakra/mantinefell 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,chakraandmantinerender through react-hook-form'sController, 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.
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
- Server Actions Pack - Integrate with Next.js server actions
- API Docs Pack - Test forms against mock API
- SDK Publisher - Use generated SDK for form submissions