Repository: colinhacks/zod
Pull request #4722 (2025-06-16 13:15:34)

chore(docs): include zod-validation-error lib to Ecosystem page
**Summary:**
This PR adds [zod-validation-error](https://github.com/causaly/zod-validation-error) to the [Ecosystem](https://zod.dev/ecosystem?id=form-integrations) page to highlight it as a useful utility for formatting and handling Zod validation errors in a developer-friendly way.

**Why:**
To improve discoverability of helpful tooling.


Patch:
diff --git a/packages/docs/components/ecosystem.tsx b/packages/docs/components/ecosystem.tsx
index 2df03f2c..62a36f72 100644
--- a/packages/docs/components/ecosystem.tsx
+++ b/packages/docs/components/ecosystem.tsx
@@ -72,6 +72,12 @@ const formIntegrations: ZodResource[] = [
     description: "Making SvelteKit forms a pleasure to use!",
     slug: "ciscoheat/sveltekit-superforms",
   },
+  {
+    name: "zod-validation-error",
+    url: "https://github.com/causaly/zod-validation-error",
+    description: "Generate user-friendly error messages from ZodError instances.",
+    slug: "causaly/zod-validation-error",
+  },
 ];
 
 const zodToXConverters: ZodResource[] = [];
diff --git a/packages/docs/content/api.mdx b/packages/docs/content/api.mdx
index 13168ad3..6aa24776 100644
--- a/packages/docs/content/api.mdx
+++ b/packages/docs/content/api.mdx
@@ -981,6 +981,10 @@ Dog.parse({ name: "Yeller", extraKey: true });
 
 To define a *catchall schema* that will be used to validate any unrecognized keys:
 
+
+
+<Tabs groupId="lib" items={["Zod", "Zod Mini"]}>
+<Tab value="Zod">
 ```ts z.object
 const DogWithStrings = z.object({
   name: z.string(),
@@ -990,6 +994,23 @@ const DogWithStrings = z.object({
 DogWithStrings.parse({ name: "Yeller", extraKey: "extraValue" }); // ✅
 DogWithStrings.parse({ name: "Yeller", extraKey: 42 }); // ❌
 ```
+</Tab>
+<Tab value="Zod Mini">
+```ts z.object
+const DogWithStrings = z.catchall(
+  z.object({
+    name: z.string(),
+    age: z.number().optional(),
+  }),
+  z.string()
+);
+
+DogWithStrings.parse({ name: "Yeller", extraKey: "extraValue" }); // ✅
+DogWithStrings.parse({ name: "Yeller", extraKey: 42 }); // ❌
+```
+</Tab>
+</Tabs>
+
 
 ### `.shape`
 
diff --git a/packages/docs/content/v4/changelog.mdx b/packages/docs/content/v4/changelog.mdx
index 882968e2..7f8900f6 100644
--- a/packages/docs/content/v4/changelog.mdx
+++ b/packages/docs/content/v4/changelog.mdx
@@ -320,6 +320,14 @@ z.string().email(); // ❌ deprecated
 z.email(); // ✅ 
 ```
 
+### stricter `.uuid()`
+
+The `z.uuid()` now validates UUIDs more strictly against the RFC 4122 specification; specifically, the variant bits must be `10` per the spec. For a more permissive "UUID-like" validator, use `z.guid()`.
+
+```ts
+z.uuid(); // RFC 4122 compliant UUID
+z.guid(); // any 8-4-4-4-12 hex pattern
+```
 
 ### no padding in `.base64url()` 
 
diff --git a/packages/zod/package.json b/packages/zod/package.json
index a34e0c09..58628a73 100644
--- a/packages/zod/package.json
+++ b/packages/zod/package.json
@@ -1,6 +1,6 @@
 {
   "name": "zod",
-  "version": "3.25.64",
+  "version": "3.25.65",
   "type": "module",
   "author": "Colin McDonnell <zod@colinhacks.com>",
   "description": "TypeScript-first schema declaration and validation library with static type inference",
diff --git a/packages/zod/src/v4/core/api.ts b/packages/zod/src/v4/core/api.ts
index 582b0577..f9811835 100644
--- a/packages/zod/src/v4/core/api.ts
+++ b/packages/zod/src/v4/core/api.ts
@@ -1460,7 +1460,7 @@ export function _custom<O = unknown, I = O>(
 // ): checks.$ZodCheck<T> {
 //   return _custom(Class, fn, _params);
 // }
-// same as _custom but deafults to abort:false
+// same as _custom but defaults to abort:false
 export function _refine<O = unknown, I = O>(
   Class: util.SchemaClass<schemas.$ZodCustom>,
   fn: (data: O) => unknown,
diff --git a/packages/zod/src/v4/mini/schemas.ts b/packages/zod/src/v4/mini/schemas.ts
index dfef934e..f14ee8ac 100644
--- a/packages/zod/src/v4/mini/schemas.ts
+++ b/packages/zod/src/v4/mini/schemas.ts
@@ -848,6 +848,13 @@ export function required(schema: ZodMiniObject, mask?: object) {
   return util.required(ZodMiniNonOptional, schema, mask);
 }
 
+export function catchall<T extends ZodMiniObject, U extends SomeType>(
+  inst: T,
+  catchall: U
+): ZodMiniObject<T["shape"], core.$catchall<U>> {
+  return inst.clone({ ...inst._zod.def, catchall: catchall as any }) as any;
+}
+
 // ZodMiniUnion
 export interface ZodMiniUnion<T extends readonly SomeType[] = readonly core.$ZodType[]>
   extends _ZodMiniType<core.$ZodUnionInternals<T>> {
diff --git a/play.ts b/play.ts
index fee6c349..593941f3 100644
--- a/play.ts
+++ b/play.ts
@@ -2,42 +2,4 @@ import { z } from "zod/v4";
 
 z;
 
-const file = z
-  .file()
-  .max(5_000_000, { message: "File too large (max 5MB)" })
-  .mime(["image/png", "image/jpeg"], { error: "Only PNG and JPEG allowed" })
-  .optional();
-
-// test parsing with invalid mime
-const f = new File([""], "test.txt", {
-  type: "text/plain",
-});
-file.parse(f);
-
-const Story = z.object({
-  type: z.literal("story"),
-  story: z.looseObject({}),
-});
-
-const Collection = z.object({
-  type: z.literal("collection"),
-  get items(): z.ZodArray<z.ZodDiscriminatedUnion<[typeof Story, typeof Collection]>> {
-    return z.array(z.discriminatedUnion("type", [Story, Collection]));
-  },
-});
-
-type Collection = z.infer<typeof Collection>;
-
-const ActivityUnion = z.union([
-  z.object({
-    name: z.string(),
-
-    // now it's `any` though the whole object is exactly the same
-    get subactivities(): z.ZodNullable<z.ZodArray<typeof ActivityUnion>> {
-      return z.nullable(z.array(ActivityUnion));
-    },
-  }),
-  z.string(),
-]);
-
-type A = z.infer<typeof ActivityUnion>;
+console.dir(z.literal(Number.POSITIVE_INFINITY).parse(Number.POSITIVE_INFINITY), { depth: null });


Test patch:
diff --git a/packages/zod/src/v4/mini/tests/object.test.ts b/packages/zod/src/v4/mini/tests/object.test.ts
index 2d354845..0d813d70 100644
--- a/packages/zod/src/v4/mini/tests/object.test.ts
+++ b/packages/zod/src/v4/mini/tests/object.test.ts
@@ -152,3 +152,34 @@ test("z.partial with mask", () => {
   expect(z.safeParse(partialSchemaWithMask, { age: 30 }).success).toBe(true);
   expect(z.safeParse(partialSchemaWithMask, { name: "John" }).success).toBe(false);
 });
+
+test("z.catchall", () => {
+  // z.catchall()
+  const schema = z.catchall(
+    z.object({
+      name: z.string(),
+      // age: z.number(),
+    }),
+    z.string()
+  );
+
+  type schemaIn = z.input<typeof schema>;
+  type schemaOut = z.output<typeof schema>;
+  expectTypeOf<schemaIn>().toEqualTypeOf<{
+    name: string;
+    [key: string]: string;
+  }>();
+
+  expectTypeOf<schemaOut>().toEqualTypeOf<{
+    name: string;
+    [key: string]: string;
+  }>();
+
+  schema.parse({
+    name: "john",
+    age: "30",
+    extra: "extra value",
+  });
+
+  expect(() => schema.parse({ name: "john", age: 30 })).toThrow();
+});