Repository: colinhacks/zod
Pull request #4379 (2025-05-16 00:40:22)

Allow generic on coerce functions
Support an optional generic parameter on `z.coerce.string()`, etc.

```ts
const a = z.coerce.string<any>();
type Ain = z.input<typeof a>; // any

const b = z.coerce.string();
type Bin = z.input<typeof a>; // unknown
```

This also lets TypeScript set the input type appropriately in inference-constrained environments.

```
import { z } from "zod/v4";

z.pipe(z.string(), z.coerce.number());
```


Patch:
diff --git a/packages/zod/src/v4/classic/coerce.ts b/packages/zod/src/v4/classic/coerce.ts
index 9ccc2f58..c5cf0c62 100644
--- a/packages/zod/src/v4/classic/coerce.ts
+++ b/packages/zod/src/v4/classic/coerce.ts
@@ -1,27 +1,27 @@
 import * as core from "zod/v4/core";
 import * as schemas from "./schemas.js";
 
-export interface ZodCoercedString extends schemas._ZodString<unknown> {}
-export function string(params?: string | core.$ZodStringParams): ZodCoercedString {
-  return core._coercedString(schemas.ZodString, params);
+export interface ZodCoercedString<T = unknown> extends schemas._ZodString<T> {}
+export function string<T = unknown>(params?: string | core.$ZodStringParams): ZodCoercedString<T> {
+  return core._coercedString(schemas.ZodString, params) as any;
 }
 
-export interface ZodCoercedNumber extends schemas._ZodNumber<unknown> {}
-export function number(params?: string | core.$ZodNumberParams): ZodCoercedNumber {
-  return core._coercedNumber(schemas.ZodNumber, params);
+export interface ZodCoercedNumber<T = unknown> extends schemas._ZodNumber<T> {}
+export function number<T = unknown>(params?: string | core.$ZodNumberParams): ZodCoercedNumber<T> {
+  return core._coercedNumber(schemas.ZodNumber, params) as ZodCoercedNumber<T>;
 }
 
-export interface ZodCoercedBoolean extends schemas._ZodBoolean<unknown> {}
-export function boolean(params?: string | core.$ZodBooleanParams): ZodCoercedBoolean {
-  return core._coercedBoolean(schemas.ZodBoolean, params);
+export interface ZodCoercedBoolean<T = unknown> extends schemas._ZodBoolean<T> {}
+export function boolean<T = unknown>(params?: string | core.$ZodBooleanParams): ZodCoercedBoolean<T> {
+  return core._coercedBoolean(schemas.ZodBoolean, params) as ZodCoercedBoolean<T>;
 }
 
-export interface ZodCoercedBigInt extends schemas._ZodBigInt<unknown> {}
-export function bigint(params?: string | core.$ZodBigIntParams): ZodCoercedBigInt {
-  return core._coercedBigint(schemas.ZodBigInt, params);
+export interface ZodCoercedBigInt<T = unknown> extends schemas._ZodBigInt<T> {}
+export function bigint<T = unknown>(params?: string | core.$ZodBigIntParams): ZodCoercedBigInt<T> {
+  return core._coercedBigint(schemas.ZodBigInt, params) as ZodCoercedBigInt<T>;
 }
 
-export interface ZodCoercedDate extends schemas._ZodDate<unknown> {}
-export function date(params?: string | core.$ZodDateParams): ZodCoercedDate {
-  return core._coercedDate(schemas.ZodDate, params);
+export interface ZodCoercedDate<T = unknown> extends schemas._ZodDate<T> {}
+export function date<T = unknown>(params?: string | core.$ZodDateParams): ZodCoercedDate<T> {
+  return core._coercedDate(schemas.ZodDate, params) as ZodCoercedDate<T>;
 }
diff --git a/packages/zod/src/v4/core/schemas.ts b/packages/zod/src/v4/core/schemas.ts
index f54a225b..11b348ea 100644
--- a/packages/zod/src/v4/core/schemas.ts
+++ b/packages/zod/src/v4/core/schemas.ts
@@ -81,11 +81,6 @@ export interface $ZodTypeDef {
   checks?: checks.$ZodCheck<never>[];
 }
 
-export const $out: unique symbol = Symbol("out");
-export type $out = typeof $out;
-export const $in: unique symbol = Symbol("in");
-export type $in = typeof $in;
-
 // @ts-ignore
 export interface $ZodTypeInternals<out O = unknown, out I = unknown> {
   /** The `@zod/core` version of this schema */
diff --git a/packages/zod/src/v4/mini/coerce.ts b/packages/zod/src/v4/mini/coerce.ts
index 7d322da6..435ccf20 100644
--- a/packages/zod/src/v4/mini/coerce.ts
+++ b/packages/zod/src/v4/mini/coerce.ts
@@ -1,22 +1,22 @@
 import * as core from "zod/v4/core";
 import * as schemas from "./schemas.js";
 
-export function string(params?: string | core.$ZodStringParams): schemas.ZodMiniString<unknown> {
-  return core._coercedString(schemas.ZodMiniString, params);
+export function string<T = unknown>(params?: string | core.$ZodStringParams): schemas.ZodMiniString<T> {
+  return core._coercedString(schemas.ZodMiniString, params) as schemas.ZodMiniString<T>;
 }
 
-export function number(params?: string | core.$ZodNumberParams): schemas.ZodMiniNumber<unknown> {
-  return core._coercedNumber(schemas.ZodMiniNumber, params);
+export function number<T = unknown>(params?: string | core.$ZodNumberParams): schemas.ZodMiniNumber<T> {
+  return core._coercedNumber(schemas.ZodMiniNumber, params) as schemas.ZodMiniNumber<T>;
 }
 
-export function boolean(params?: string | core.$ZodBooleanParams): schemas.ZodMiniBoolean<unknown> {
-  return core._coercedBoolean(schemas.ZodMiniBoolean, params);
+export function boolean<T = unknown>(params?: string | core.$ZodBooleanParams): schemas.ZodMiniBoolean<T> {
+  return core._coercedBoolean(schemas.ZodMiniBoolean, params) as schemas.ZodMiniBoolean<T>;
 }
 
-export function bigint(params?: string | core.$ZodBigIntParams): schemas.ZodMiniBigInt<unknown> {
-  return core._coercedBigint(schemas.ZodMiniBigInt, params);
+export function bigint<T = unknown>(params?: string | core.$ZodBigIntParams): schemas.ZodMiniBigInt<T> {
+  return core._coercedBigint(schemas.ZodMiniBigInt, params) as schemas.ZodMiniBigInt<T>;
 }
 
-export function date(params?: string | core.$ZodDateParams): schemas.ZodMiniDate<unknown> {
-  return core._coercedDate(schemas.ZodMiniDate, params);
+export function date<T = unknown>(params?: string | core.$ZodDateParams): schemas.ZodMiniDate<T> {
+  return core._coercedDate(schemas.ZodMiniDate, params) as schemas.ZodMiniDate<T>;
 }
diff --git a/play.ts b/play.ts
index 0d285f7f..c98e394a 100644
--- a/play.ts
+++ b/play.ts
@@ -1,28 +1,3 @@
 import { z } from "zod/v4";
 
-const schema = z.object({
-  username: z.string(),
-  coworkers: z.array(
-    z.object({
-      name: z.string(),
-      children: z.array(
-        z.object({
-          name: z.string(),
-        })
-      ),
-    })
-  ),
-});
-
-const data = {
-  username: "John",
-  coworkers: [
-    { name: "Dave", children: [{ name: "Luke" }] },
-    { name: "Amy", children: [{ name: "Ana" }, { name: 3 }] },
-  ],
-};
-
-const result = schema.safeParse(data);
-console.dir(z.formatError(result.error!), { depth: null }); // false
-
-z.treeifyError(result.error!).properties?.username?.errors; // true
+z.pipe(z.string(), z.coerce.number());


Test patch:
diff --git a/packages/zod/src/v4/classic/tests/coerce.test.ts b/packages/zod/src/v4/classic/tests/coerce.test.ts
index 71345c5a..136a3827 100644
--- a/packages/zod/src/v4/classic/tests/coerce.test.ts
+++ b/packages/zod/src/v4/classic/tests/coerce.test.ts
@@ -1,4 +1,4 @@
-import { expect, test } from "vitest";
+import { expect, expectTypeOf, test } from "vitest";
 
 import * as z from "zod/v4";
 
@@ -150,3 +150,11 @@ test("date coercion", () => {
 //   expect(schema.parse("300vmax")).toEqual("300vmax");
 //   expect(schema.parse(["300px"])).toEqual("300px");
 // });
+
+test("override input type", () => {
+  const a = z.coerce.string<any>();
+  type input = z.input<typeof a>;
+  expectTypeOf<input>().toEqualTypeOf<any>();
+  type output = z.infer<typeof a>;
+  expectTypeOf<output>().toEqualTypeOf<string>();
+});