agent_patch
runs/2026-02-27__21-30-28__gpt-5-4/flux-pr-4567/flux-pr-4567.1-of-1.2026-02-27__21-30-28__gpt-5-4/agent-logs/agent.patch
6672 bytes
diff --git a/tmp/agent-patch-flux-pr-4567.1-of-1.2026-02-27__21-30-28__gpt-5-4/app/packages/docs/content/api.mdx b/app/packages/docs/content/api.mdx
index e673ea9..b0941df 100644
--- a/tmp/agent-patch-flux-pr-4567.1-of-1.2026-02-27__21-30-28__gpt-5-4/app/packages/docs/content/api.mdx
+++ b/app/packages/docs/content/api.mdx
@@ -1630,6 +1630,31 @@ z.set(z.string()).check(z.size(5)); // must contain 5 items exactly
</Tab>
</Tabs>
+## Files
+
+To validate `File` inputs:
+
+<Tabs groupId="lib" items={["Zod", "Zod Mini"]}>
+<Tab value="Zod">
+```ts
+const fileSchema = z.file();
+
+fileSchema.min(10_000);
+fileSchema.max(1_000_000);
+fileSchema.mime(["image/png"]);
+```
+</Tab>
+<Tab value='zod/v4-mini'>
+```ts
+const fileSchema = z.file().check(
+ z.minSize(10_000),
+ z.maxSize(1_000_000),
+ z.mime(["image/png"]),
+);
+```
+</Tab>
+</Tabs>
+
## Promises
<Callout type="warn">
@@ -2515,4 +2540,3 @@ const MyFunction = z.function({
const computeTrimmedLength = MyFunction.implement((input) => input.trim.length);
```
-
diff --git a/tmp/agent-patch-flux-pr-4567.1-of-1.2026-02-27__21-30-28__gpt-5-4/app/packages/docs/content/json-schema.mdx b/app/packages/docs/content/json-schema.mdx
index 7039b2d..9eed9c8 100644
--- a/tmp/agent-patch-flux-pr-4567.1-of-1.2026-02-27__21-30-28__gpt-5-4/app/packages/docs/content/json-schema.mdx
+++ b/app/packages/docs/content/json-schema.mdx
@@ -39,7 +39,6 @@ z.void(); // ❌
z.date(); // ❌
z.map(); // ❌
z.set(); // ❌
-z.file(); // ❌
z.transform(); // ❌
z.nan(); // ❌
z.custom(); // ❌
@@ -70,6 +69,26 @@ These schemas are supported via `contentEncoding`:
z.base64(); // => { type: "string", contentEncoding: "base64" }
```
+## Files
+
+Zod represents `z.file()` as a binary string schema and preserves file-specific validation metadata on the emitted JSON Schema:
+
+```ts
+const schema = z.toJSONSchema(
+ z.file().min(10_000).max(1_000_000).mime(["image/png", "image/jpeg"])
+);
+
+// => {
+// type: "string",
+// format: "binary",
+// minSize: 10_000,
+// maxSize: 1_000_000,
+// mime: ["image/png", "image/jpeg"]
+// }
+```
+
+When a file schema accepts exactly one MIME type, Zod also emits `contentMediaType` for compatibility with standard JSON Schema content metadata.
+
All other string formats are supported via `pattern`:
```ts
@@ -312,7 +331,6 @@ z.void(); // ❌
z.date(); // ❌
z.map(); // ❌
z.set(); // ❌
-z.file(); // ❌
z.transform(); // ❌
z.nan(); // ❌
z.custom(); // ❌
diff --git a/tmp/agent-patch-flux-pr-4567.1-of-1.2026-02-27__21-30-28__gpt-5-4/app/packages/docs/content/v4/index.mdx b/app/packages/docs/content/v4/index.mdx
index 686841c..bd31814 100644
--- a/tmp/agent-patch-flux-pr-4567.1-of-1.2026-02-27__21-30-28__gpt-5-4/app/packages/docs/content/v4/index.mdx
+++ b/app/packages/docs/content/v4/index.mdx
@@ -584,7 +584,7 @@ const fileSchema = z.file();
fileSchema.min(10_000); // minimum .size (bytes)
fileSchema.max(1_000_000); // maximum .size (bytes)
-fileSchema.type("image/png"); // MIME type
+fileSchema.mime(["image/png"]); // MIME type
```
## Internationalization
diff --git a/tmp/agent-patch-flux-pr-4567.1-of-1.2026-02-27__21-30-28__gpt-5-4/app/packages/zod/src/v4/classic/tests/to-json-schema.test.ts b/app/packages/zod/src/v4/classic/tests/to-json-schema.test.ts
index 57efa33..85009b0 100644
--- a/tmp/agent-patch-flux-pr-4567.1-of-1.2026-02-27__21-30-28__gpt-5-4/app/packages/zod/src/v4/classic/tests/to-json-schema.test.ts
+++ b/app/packages/zod/src/v4/classic/tests/to-json-schema.test.ts
@@ -237,10 +237,6 @@ describe("toJSONSchema", () => {
expect(() => z.toJSONSchema(z.set(z.string()))).toThrow("Set cannot be represented in JSON Schema");
expect(() => z.toJSONSchema(z.custom(() => true))).toThrow("Custom types cannot be represented in JSON Schema");
- // File type
- const fileSchema = z.file();
- expect(() => z.toJSONSchema(fileSchema)).toThrow("File cannot be represented in JSON Schema");
-
// Transform
const transformSchema = z.string().transform((val) => Number.parseInt(val));
expect(() => z.toJSONSchema(transformSchema)).toThrow("Transforms cannot be represented in JSON Schema");
@@ -260,6 +256,43 @@ describe("toJSONSchema", () => {
expect(() => z.toJSONSchema(dynamicCatchSchema)).toThrow("Dynamic catch values are not supported in JSON Schema");
});
+ test("file schemas", () => {
+ expect(z.toJSONSchema(z.file())).toMatchInlineSnapshot(`
+ {
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "format": "binary",
+ "type": "string",
+ }
+ `);
+
+ const fileSchema = z.file().min(10_000).max(1_000_000).mime(["image/png", "image/jpeg"]);
+ expect(z.toJSONSchema(fileSchema)).toMatchInlineSnapshot(`
+ {
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "format": "binary",
+ "maxSize": 1000000,
+ "mime": [
+ "image/png",
+ "image/jpeg",
+ ],
+ "minSize": 10000,
+ "type": "string",
+ }
+ `);
+
+ expect(z.toJSONSchema(z.file().mime(["image/png"]))).toMatchInlineSnapshot(`
+ {
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "contentMediaType": "image/png",
+ "format": "binary",
+ "mime": [
+ "image/png",
+ ],
+ "type": "string",
+ }
+ `);
+ });
+
test("string formats", () => {
expect(z.toJSONSchema(z.string().email())).toMatchInlineSnapshot(`
{
diff --git a/tmp/agent-patch-flux-pr-4567.1-of-1.2026-02-27__21-30-28__gpt-5-4/app/packages/zod/src/v4/core/to-json-schema.ts b/app/packages/zod/src/v4/core/to-json-schema.ts
index ca1a680..7efa5da 100644
--- a/tmp/agent-patch-flux-pr-4567.1-of-1.2026-02-27__21-30-28__gpt-5-4/app/packages/zod/src/v4/core/to-json-schema.ts
+++ b/app/packages/zod/src/v4/core/to-json-schema.ts
@@ -434,8 +434,16 @@ export class JSONSchemaGenerator {
break;
}
case "file": {
- if (this.unrepresentable === "throw") {
- throw new Error("File cannot be represented in JSON Schema");
+ const json: JSONSchema.BaseSchema = _json as any;
+ const { minimum, maximum, mime } = schema._zod.bag;
+
+ json.type = "string";
+ json.format = "binary";
+ if (typeof minimum === "number") json.minSize = minimum;
+ if (typeof maximum === "number") json.maxSize = maximum;
+ if (Array.isArray(mime) && mime.length > 0) {
+ json.mime = mime;
+ if (mime.length === 1) json.contentMediaType = mime[0];
}
break;
}