Use Zod 4 for your Helix function and tool schemas. New projects from helix init scaffold it. Projects on Zod 3.25 or later keep working, so you can migrate when it suits you: update your zod dependency and adjust the few schema patterns that changed. Most schemas need no change.
Update the dependency
Set your project’s zod to ^4.6.6 and install:
npm install zod@^4.6.6
The SDK does not re-export zod, so your project declares its own, listed in package.json next to @trayai/helix-sdk. New projects already scaffold zod@^4.6.6.
Clear the security advisory
Zod versions before 4.6.6 carry a high-severity advisory, GHSA-mrhx-c7xc-m3w5. An array schema with no length limit records a validation issue for every failing element, so validating a very large input can exhaust memory and crash the process.
This is only reachable if one of your functions validates untrusted input with an unbounded z.array(...). Moving to 4.6.6 or later and bounding those arrays closes it:
import { z } from 'zod';
export const input = z.object({
// Cap arrays that come from untrusted callers
tags: z.array(z.string()).max(100),
});
The .max(...) bound is good practice on any Zod version.
Update your schemas
These are the Zod 3 patterns that change in Zod 4. Zod’s v4 changelog is the full reference.
z.recordneeds two arguments.z.record(valueSchema)becomesz.record(z.string(), valueSchema).z.uuid()is stricter. It now accepts only standard 8-4-4-4-12 UUIDs. Usez.guid()for other hex IDs.ZodError.errorsis removed. Read.issuesinstead..default()short-circuits. When the input isundefined, the default is returned without re-parsing. Use.prefault()where you want the default parsed.- Top-level string formats. Prefer
z.email(),z.url(), andz.uuid(). The method forms likez.string().email()still work but are deprecated. z.unknown()andz.any()keys are now required. An object key whose value isz.unknown()orz.any()must be present at parse time. Add.optional()to keep the old behavior.
Compose the SDK’s exported schemas
The SDK exports a few schemas for validating Helix identifiers. On Zod 4, compose these into your own object schemas:
| Export | Validates |
|---|---|
region.schema | A Helix region name. |
projectIdSchema | A project ID. |
workspaceIdSchema | A workspace ID. |
import { z } from 'zod';
import { projectIdSchema, region } from '@trayai/helix-sdk';
export const input = z.object({
projectId: projectIdSchema,
region: region.schema,
});
These are Zod 4 schemas, so you nest them inside your Zod 4 objects. You can also call .parse() or .safeParse() on any of them directly. The region export also provides region.values, the region names Helix accepts, and region.isKnown(name), which returns true for an accepted name. Neither needs Zod.
Automate it with an AI assistant
If you build with Claude Code or another coding agent, this prompt migrates a project’s own schemas:
Upgrade this project from Zod 3 to Zod 4.
1. Set "zod" to "^4.6.6" in package.json and install.
2. Update my Zod schemas for Zod 4:
- z.record(value) -> z.record(z.string(), value)
- z.string().uuid() -> z.uuid(); use z.guid() for non-RFC hex IDs
- ZodError.errors -> .issues
- .default() that should re-parse -> .prefault()
- z.string().email()/.url()/.uuid() -> z.email()/z.url()/z.uuid()
- add .optional() to any z.unknown()/z.any() object key that should stay optional
3. Add .max(...) to any z.array() that validates untrusted input.
4. Run the type checker and tests, and fix anything that breaks.
Leave schemas that already work unchanged.
Verify the upgrade
Run your type checker and tests, and fix anything that breaks:
npm run typecheck
npm test
Then run helix dev and exercise a function that validates input, so you catch any schema that parses differently under Zod 4.