Breaking changes
1. Only Zod v4 is supported
Remix Forms v4 removed support for Zod v3. Before upgrading Remix Forms, please upgrade your Zod dependency to v4.
If you cannot upgrade to Zod v4 yet, Remix Forms 3.1.1 is stable and will continue working with Zod v3.
How to upgrade
Update your Zod dependency to v4:
npm install zod@latestMost Remix Forms users won't need to change their code after upgrading Zod. However, if you use advanced Zod features, you may need to make some adjustments. The most common breaking changes in Zod v4 that may affect your schemas are:
Default behavior in optional fields
Zod v4 now applies defaults inside properties even within optional fields. This change aligns better with expectations but may cause breakage if your code relies on key existence.
Before (Zod v3):
const schema = z.object({
name: z.string(),
role: z.string().default('user').optional(),
})
schema.parse({ name: 'Alice' })
// Result: { name: 'Alice' }After (Zod v4):
const schema = z.object({
name: z.string(),
role: z.string().default('user').optional(),
})
schema.parse({ name: 'Alice' })
// Result: { name: 'Alice', role: 'user' }Object methods replaced with top-level functions
The .strict() and .passthrough() methods have been replaced with top-level functions.
Before (Zod v3):
const schema = z.object({ name: z.string() }).strict()
const schema2 = z.object({ name: z.string() }).passthrough()After (Zod v4):
const schema = z.strictObject({ name: z.string() })
const schema2 = z.looseObject({ name: z.string() })Function API changes
If you use z.function(), the API has changed significantly. The result is no longer a Zod schema but a "function factory" for defining Zod-validated functions.
Before (Zod v3):
const myFunction = z.function()
.args(z.string(), z.number())
.returns(z.boolean())After (Zod v4):
const myFunction = z.function(
z.tuple([z.string(), z.number()]),
z.boolean(),
)Array .nonempty() behavior
The .nonempty() method now behaves identically to .min(1). The inferred type does not change, but if you relied on the old behavior for type narrowing, consider using z.tuple() instead.
For a complete list of Zod v4 breaking changes, see the official Zod v4 migration guide.
Minor changes
2. New exports: objectFromSchema and ObjectFromSchema
We now export two new utilities that were previously internal:
objectFromSchema: A function that creates a default object from a Zod schemaObjectFromSchema: A TypeScript type that infers the object type from a schema
These can be useful for creating initial form values:
import { objectFromSchema, type ObjectFromSchema } from 'remix-forms'
import { z } from 'zod'
const schema = z.object({
name: z.string().default(''),
age: z.number().default(0),
})
const initialValues = objectFromSchema(schema)
// Result: { name: '', age: 0 }
type FormData = ObjectFromSchema<typeof schema>
// Type: { name: string; age: number }What's Changed
- Add missing shapeInfo enum test by @danielweinmann in #354
- Add missing mutation error test by @danielweinmann in #356
- Add missing boolean coercion and navigation tests by @danielweinmann in #355
- Add tests for mutation transformResult hooks by @danielweinmann in #357
- Fix flaky zod-effects test by @danielweinmann in #359
- Fix e2e test flakiness by @danielweinmann in #360
- Fix flaky test by mocking router early by @danielweinmann in #361
- Split long e2e tests by @danielweinmann in #362
- Split slow tests into separate files by @danielweinmann in #363
- Split slow tests by @danielweinmann in #365
- Refactor coerceToForm by @danielweinmann in #366
- Split slow e2e tests by @danielweinmann in #367
- Split some e2e specs for parallel test runs by @danielweinmann in #374
- Add Claude instructions by @felipefreitag in #379
- .github/workflows: Migrate workflows to Blacksmith runners by @blacksmith-sh[bot] in #382
- [BREAKING CHANGE] Support Zod4 and drop Zod3 support by @danielweinmann in #383
- Upgrade React Router to 7.9.4 and export objectFromSchema and ObjectFromSchema by @danielweinmann in #384
New Contributors
- @blacksmith-sh[bot] made their first contribution in #382
Full Changelog: v3.1.1...v4.0.0