Skip to content

Conversation

@Ayush2k02
Copy link

@Ayush2k02 Ayush2k02 commented Dec 25, 2025

Fixes #5544

Adds a mutable z.ext namespace to allow community plugins to extend Zod without modifying the frozen core z object. This addresses the issue where direct property assignment fails in production builds.

Example of failing code:

// Works in dev, breaks in prod
z.iso.timezone = () => z.string().check(...);
// TypeError: Cannot assign to read only property

Solution

Implemented z.ext as a dedicated, mutable namespace specifically for plugins. This approach:

  • ✅ Maintains discoverability through z.ext.* with IDE autocomplete
  • ✅ Avoids any breaking changes to the core z object

Usage Example

Plugin Author:

import { z } from 'zod';

// Register plugin on z.ext
z.ext.cron = () => {
  return z.string().refine(
    (val) => /^(\*|([0-5]?\d)) (\*|([01]?\d|2[0-3]))/.test(val),
    { message: "Invalid cron expression" }
  );
};

// TypeScript support via declaration merging
declare module 'zod' {
  namespace z {
    namespace ext {
      function cron(): ZodString;
    }
  }
}

Consumer:

import 'my-zod-cron-plugin'; // Side-effect import
import { z } from 'zod';

const cronSchema = z.ext.cron();
cronSchema.parse("0 0"); // ✅ Valid
cronSchema.parse("invalid"); // ❌ Throws error

Test Results

  • ✅ Basic plugin extension
  • ✅ Multiple plugins coexisting
  • ✅ Core z object remains unaffected

Fixes colinhacks#5544

Adds a mutable z.ext namespace to allow community plugins to extend
Zod without modifying the frozen core z object. This addresses the
issue where direct property assignment fails in production builds.

Problem:
In issue colinhacks#5544, users reported that direct property assignment to
the z namespace (e.g., z.iso.timezone = ...) works in development
but throws "TypeError: Cannot assign to read only property" in
production builds due to object freezing.

Solution:
Implemented z.ext as a dedicated, mutable namespace specifically for
plugins. This approach:

- Is bundler-safe and works in all production environments
- Provides a standardized format for community plugins
- Maintains discoverability through z.ext.* with IDE autocomplete
- Avoids any breaking changes to the core z object

Implementation:
- Created packages/zod/src/v4/classic/plugins.ts with ext namespace
- Exported ext from external.ts to make it available on z
- Added comprehensive tests in plugins.test.ts demonstrating usage
- Included PLUGIN_API.md documentation with examples and best practices

The implementation follows the maintainer's suggestion to use a
dedicated extension namespace rather than a full plugin registration
system, avoiding the issues that have affected libraries like dayjs.

Usage example:
```typescript
// Plugin author
z.ext.cron = () => z.string().refine(...)

// Consumer
import 'my-zod-cron-plugin'
const schema = z.ext.cron()
```
@pullfrog
Copy link
Contributor

pullfrog bot commented Dec 25, 2025

Pullfrog  | Review this ➔pullfrog.com

@pullfrog
Copy link
Contributor

pullfrog bot commented Dec 25, 2025

Pullfrog  | Review this ➔pullfrog.com

@Ayush2k02
Copy link
Author

Hey, any updates on this !

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Proposal: z.registerPlugin() API for extending Zod

1 participant