Repository: colinhacks/zod
Pull request #4844 (2025-07-09 04:19:23)

v4.0.0
For the last 6 weeks, the stable release of Zod 4 has been available at a subpath `"zod/v4"` alongside Zod 3 (`zod@3.25.x`). This PR finally publishes `zod@4.0.0` to npm, completing the incremental release plan laid out in the [Zod 4 versioning writeup](https://github.com/colinhacks/zod/issues/4371).

- `zod@4.0.0` is now published as `latest` on `npm`
- the package root (`"zod"`) now exports Zod 4 instead of Zod 3
- existing subpath imports (`"zod/v4"` and `"zod/v4-mini"`) will continue to work forever 

### Upgrading

To upgrade to `zod@4`:

```
npm upgrade zod@latest
```

If you have already been using Zod 4: your existing imports (`"zod/v4"` and `"zod/v4-mini"`) will continue to work forever. However, after upgrading, you can *optionally* rewrite your imports to the following simplified versions:

|                | Before   | After     |
|----------------|-----------------|-------------|
| Zod 4          | `"zod/v4"`      | `"zod"`     |
| Zod 4 Mini     | `"zod/v4-mini"` | `"zod/mini"`|
| Zod 3          | `"zod"`         | `"zod/v3"`  |

### Library authors

If you've already implemented Zod 4 support according to the best practices outlined in the [Library authors](/library-authors) guide, bump your peer dependency to include `zod@^4.0.0`:

```json
// package.json
{
  "peerDependencies": {
    "zod": "^3.25.0 || ^4.0.0"
  }
}
```

*There should be no other code changes necessary.* No code changes were made between the latest `3.25.x` release and `4.0.0`. This does not require a major version bump.

### Why was this all necessary?

Refer to the [Zod 4 versioning writeup](https://github.com/colinhacks/zod/issues/4371) for complete information.


Patch:
diff --git a/.cursor/rules/zod-project-guide.mdc b/.cursor/rules/zod-project-guide.mdc
index a7f34436..619c55a1 100644
--- a/.cursor/rules/zod-project-guide.mdc
+++ b/.cursor/rules/zod-project-guide.mdc
@@ -40,7 +40,6 @@ This is the Zod TypeScript-first schema validation library project. The project
 ### Publishing
 - `pnpm prepublishOnly` - Run tests and build before publishing
 - `pnpm publish:jsr` - Publish to JSR (dry run)
-- `pnpm pub:beta` - Publish beta version
 
 ## Key Files
 
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index d63f5ce8..06a1551b 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -40,10 +40,13 @@ jobs:
         uses: JS-DevTools/npm-publish@v3
         with:
           token: ${{ secrets.NPM_TOKEN }}
-          dry-run: false
+          # dry-run: true
           provenance: true
           package: ./packages/zod
 
+      - run: pnpm jsr publish --allow-slow-types # --dry-run
+        working-directory: ./packages/zod
+
       - name: Post-publish
         if: ${{ steps.publish.outputs.type }}
         run: |
diff --git a/.husky/pre-commit b/.husky/pre-commit
index 0b0e678b..22cdd70a 100755
--- a/.husky/pre-commit
+++ b/.husky/pre-commit
@@ -3,5 +3,5 @@ if [ -n "$(git ls-files --others --exclude-standard)" ]; then
   git status;
   exit 1
 fi
-pnpm semver-check
+pnpm check:semver
 lint-staged --verbose
diff --git a/.husky/pre-push b/.husky/pre-push
index 93ab5958..ca1e558b 100755
--- a/.husky/pre-push
+++ b/.husky/pre-push
@@ -3,5 +3,5 @@ if [ -n "$(git ls-files --others --exclude-standard)" ]; then
   git status;
   exit 1
 fi
-pnpm semver-check
+pnpm check:semver
 pnpm test
diff --git a/package.json b/package.json
index 562c1808..f9283733 100644
--- a/package.json
+++ b/package.json
@@ -19,6 +19,7 @@
     "@biomejs/biome": "^1.9.4",
     "@types/benchmark": "^2.1.5",
     "@types/node": "^20.17.30",
+    "@types/semver": "^7.7.0",
     "@web-std/file": "^3.0.3",
     "arktype": "^2.1.19",
     "benchmark": "^2.1.4",
@@ -46,7 +47,7 @@
     "vitest": "^2.1.9",
     "zod": "workspace:*",
     "zod3": "npm:zod@~3.24.0",
-    "zshy": "^0.0.12"
+    "zshy": "^0.0.13"
   },
   "lint-staged": {
     "packages/*/src/**/*.ts": [
@@ -82,7 +83,6 @@
     "publish:jsr": "jsr publish --dry-run",
     "bundle:rollup": "rollup -c",
     "bundle:esbuild": "esbuild --bundle ./scratch/input.ts --outfile=./scratch/out_esbuild.js --bundle --format=esm",
-    "pub:beta": "pnpm run -r pub:beta",
-    "semver-check": "tsx scripts/semver-check.ts"
+    "check:semver": "tsx scripts/check-semver.ts"
   }
 }
diff --git a/packages/bench/error-handling.ts b/packages/bench/error-handling.ts
index 3976fb39..e4236aca 100644
--- a/packages/bench/error-handling.ts
+++ b/packages/bench/error-handling.ts
@@ -2,7 +2,7 @@ import { makeData } from "./benchUtil.js";
 import { metabench } from "./metabench.js";
 
 import * as z4 from "zod/v4";
-import * as z from "zod";
+import * as z from "zod/v3";
 
 const a = z4.object({ a: z4.string() });
 const b = z.object({ a: z.string() });
diff --git a/packages/bench/jit-union.ts b/packages/bench/jit-union.ts
index 910c2e6e..3aa4041a 100644
--- a/packages/bench/jit-union.ts
+++ b/packages/bench/jit-union.ts
@@ -1,7 +1,7 @@
 import { makeData, randomPick, randomString } from "./benchUtil.js";
 import { metabench } from "./metabench.js";
 
-import * as z4 from "zod/v4-mini";
+import * as z4 from "zod/mini";
 
 const z4fields = {
   data1: z4.string(),
diff --git a/packages/docs/content/api.mdx b/packages/docs/content/api.mdx
index 6d7a4982..5c985268 100644
--- a/packages/docs/content/api.mdx
+++ b/packages/docs/content/api.mdx
@@ -11,7 +11,7 @@ To validate data, you must first define a *schema*. Schemas represent *types*, f
 ## Primitives
 
 ```ts
-import * as z from "zod/v4";
+import * as z from "zod";
 
 // primitive types
 z.string();
@@ -136,7 +136,7 @@ z.string().startsWith("fourscore")
 ```
 </Tab>
 <Tab value="Zod Mini">
-```ts zod/v4-mini
+```ts
 z.string().check(z.startsWith("fourscore"))
 ```
 </Tab>
@@ -151,7 +151,7 @@ z.string().startsWith("fourscore", {error: "Nice try, buddy"})
 ```
 </Tab>
 <Tab value="Zod Mini">
-```ts zod/v4-mini
+```ts
 z.string().check(z.startsWith("fourscore", {error: "Nice try, buddy"}))
 ```
 </Tab></Tabs> */}
@@ -174,7 +174,7 @@ z.string().lowercase();
 ```
 </Tab>
 <Tab value="Zod Mini">
-```ts zod/v4-mini
+```ts
 z.string().check(z.maxLength(5));
 z.string().check(z.minLength(5));
 z.string().check(z.length(5));
@@ -199,7 +199,7 @@ z.string().toUpperCase(); // toUpperCase
 ```
 </Tab>
 <Tab value="Zod Mini">
-```ts zod/v4-mini
+```ts
 z.string().check(z.trim()); // trim whitespace
 z.string().check(z.toLowerCase()); // toLowerCase
 z.string().check(z.toUpperCase()); // toUpperCase
@@ -532,7 +532,7 @@ z.number().multipleOf(5);              // alias .step(5)
 ```
 </Tab>
 <Tab value="Zod Mini">
-```ts zod/v4-mini
+```ts
 z.number().check(z.gt(5));
 z.number().check(z.gte(5));            // alias .minimum(5)
 z.number().check(z.lt(5));
@@ -587,7 +587,7 @@ z.bigint().multipleOf(5n);             // alias `.step(5n)`
 ```
 </Tab>
 <Tab value="Zod Mini">
-```ts zod/v4-mini
+```ts
 z.bigint().check(z.gt(5n));
 z.bigint().check(z.gte(5n));           // alias `.minimum(5n)`
 z.bigint().check(z.lt(5n));
@@ -638,7 +638,7 @@ z.date().max(new Date(), { error: "Too young!" });
 ```
 </Tab>
 <Tab value="Zod Mini">
-```ts zod/v4-mini
+```ts
 z.date().check(z.minimum(new Date("1900-01-01"), { error: "Too old!" }));
 z.date().check(z.maximum(new Date(), { error: "Too young!" }));
 ```
@@ -710,7 +710,7 @@ FishEnum.enum;
 ```
 </Tab>
 <Tab value="Zod Mini">
-```ts zod/v4-mini
+```ts
 const FishEnum = z.enum(["Salmon", "Tuna", "Trout"]);
 
 FishEnum.def.entries;
@@ -731,7 +731,7 @@ const TunaOnly = FishEnum.exclude(["Salmon", "Trout"]);
 ```
 </Tab>
 <Tab value="Zod Mini">
-```ts zod/v4-mini
+```ts
 // no equivalent
  
 ```
@@ -750,7 +750,7 @@ const SalmonAndTroutOnly = FishEnum.extract(["Salmon", "Trout"]);
 ```
 </Tab>
 <Tab value="Zod Mini">
-```ts zod/v4-mini
+```ts
 // no equivalent
  
 ```
@@ -813,7 +813,7 @@ z.optional(z.literal("yoda")); // or z.literal("yoda").optional()
 ```
 </Tab>
 <Tab value="Zod Mini">
-```ts zod/v4-mini
+```ts
 z.optional(z.literal("yoda"));
 ```
 </Tab>
@@ -828,7 +828,7 @@ optionalYoda.unwrap(); // ZodLiteral<"yoda">
 ```
 </Tab>
 <Tab value="Zod Mini">
-```ts zod/v4-mini
+```ts
 optionalYoda.def.innerType; // ZodMiniLiteral<"yoda">
 ```
 </Tab>
@@ -845,7 +845,7 @@ z.nullable(z.literal("yoda")); // or z.literal("yoda").nullable()
 ```
 </Tab>
 <Tab value="Zod Mini">
-```ts zod/v4-mini
+```ts
 const nullableYoda = z.nullable(z.literal("yoda"));
 ```
 </Tab>
@@ -860,7 +860,7 @@ nullableYoda.unwrap(); // ZodLiteral<"yoda">
 ```
 </Tab>
 <Tab value="Zod Mini">
-```ts zod/v4-mini
+```ts
 nullableYoda.def.innerType; // ZodMiniLiteral<"yoda">
 ```
 </Tab>
@@ -877,7 +877,7 @@ const nullishYoda = z.nullish(z.literal("yoda"));
 ```
 </Tab>
 <Tab value="Zod Mini">
-```ts zod/v4-mini
+```ts
 const nullishYoda = z.nullish(z.literal("yoda"));
 ```
 </Tab>
@@ -1024,7 +1024,7 @@ Dog.shape.age; // => number schema
 ```
 </Tab>
 <Tab value="Zod Mini">
-```ts zod/v4-mini
+```ts
 Dog.def.shape.name; // => string schema
 Dog.def.shape.age; // => number schema
 ```
@@ -1043,7 +1043,7 @@ const keySchema = Dog.keyof();
 ```
 </Tab>
 <Tab value="Zod Mini">
-```ts zod/v4-mini
+```ts
 const keySchema = z.keyof(Dog);
 // => ZodEnum<["name", "age"]>
 ```
@@ -1063,7 +1063,7 @@ const DogWithBreed = Dog.extend({
 ```
 </Tab>
 <Tab value="Zod Mini">
-```ts zod/v4-mini
+```ts
 const DogWithBreed = z.extend(Dog, {
   breed: z.string(),
 });
@@ -1115,7 +1115,7 @@ const JustTheTitle = Recipe.pick({ title: true });
 ```
 </Tab>
 <Tab value="Zod Mini">
-```ts zod/v4-mini
+```ts
 const JustTheTitle = z.pick(Recipe, { title: true });
 ```
 </Tab>
@@ -1133,7 +1133,7 @@ const RecipeNoId = Recipe.omit({ id: true });
 ```
 </Tab>
 <Tab value="Zod Mini">
-```ts zod/v4-mini
+```ts
 const RecipeNoId = z.omit(Recipe, { id: true });
 ```
 </Tab>
@@ -1155,7 +1155,7 @@ const PartialRecipe = Recipe.partial();
 ```
 </Tab>
 <Tab value="Zod Mini">
-```ts zod/v4-mini
+```ts
 const PartialRecipe = z.partial(Recipe);
 // { title?: string | undefined; description?: string | undefined; ingredients?: string[] | undefined }
 ```
@@ -1174,7 +1174,7 @@ const RecipeOptionalIngredients = Recipe.partial({
 ```
 </Tab>
 <Tab value="Zod Mini">
-```ts zod/v4-mini
+```ts
 const RecipeOptionalIngredients = z.partial(Recipe, {
   ingredients: true,
 });
@@ -1197,7 +1197,7 @@ const RequiredRecipe = Recipe.required();
 ```
 </Tab>
 <Tab value="Zod Mini">
-```ts zod/v4-mini
+```ts
 const RequiredRecipe = z.required(Recipe);
 // { title: string; description: string; ingredients: string[] }
 ```
@@ -1214,7 +1214,7 @@ const RecipeRequiredDescription = Recipe.required({description: true});
 ```
 </Tab>
 <Tab value="Zod Mini">
-```ts zod/v4-mini
+```ts
 const RecipeRequiredDescription = z.required(Recipe, {description: true});
 // { title: string; description: string; ingredients: string[] }
 ```
@@ -1353,7 +1353,7 @@ const Activity = z.object({
     When in doubt, you can generally sidestep these issues with some carefully deployed type annotations on your getters. Due to the limitations described above, this is particularly necessary when using Zod Mini.
 
     ```ts
-    import * as z from "zod/v4";
+    import * as z from "zod";
 
     const Activity = z.object({
       name: z.string(),
@@ -1377,7 +1377,7 @@ const stringArray = z.array(z.string()); // or z.string().array()
 ```
 </Tab>
 <Tab value="Zod Mini">
-```ts zod/v4-mini
+```ts
 const stringArray = z.array(z.string());
 ```
 </Tab>
@@ -1392,7 +1392,7 @@ stringArray.unwrap(); // => string schema
 ```
 </Tab>
 <Tab value="Zod Mini">
-```ts zod/v4-mini
+```ts
 stringArray.def.element; // => string schema
 ```
 </Tab>
@@ -1409,7 +1409,7 @@ z.array(z.string()).length(5); // must contain 5 items exactly
 ```
 </Tab>
 <Tab value="Zod Mini">
-```ts zod/v4-mini
+```ts
 z.array(z.string()).check(z.minLength(5)); // must contain 5 or more items
 z.array(z.string()).check(z.maxLength(5)); // must contain 5 or fewer items
 z.array(z.string()).check(z.length(5)); // must contain 5 items exactly
@@ -1462,7 +1462,7 @@ stringOrNumber.options; // [ZodString, ZodNumber]
 ```
 </Tab>
 <Tab value="Zod Mini">
-```ts zod/v4-mini
+```ts
 stringOrNumber.def.options; // [ZodString, ZodNumber]
 ```
 </Tab>
@@ -1690,7 +1690,7 @@ z.set(z.string()).max(5); // must contain 5 or fewer items
 z.set(z.string()).size(5); // must contain 5 items exactly
 ```
 </Tab>
-<Tab value='zod/v4-mini'>
+<Tab value='Zod Mini'>
 ```ts
 z.set(z.string()).check(z.minSize(5)); // must contain 5 or more items
 z.set(z.string()).check(z.maxSize(5)); // must contain 5 or fewer items
@@ -1714,7 +1714,7 @@ fileSchema.mime(["image/png"]); // MIME type
 ```
 
 </Tab>
-<Tab value='zod/v4-mini'>
+<Tab value='Zod Mini'>
 ```ts
 const fileSchema = z.file();
 
@@ -1796,7 +1796,7 @@ const myString = z.string().refine((val) => val.length <= 255);
 ```
 </Tab>
 <Tab value="Zod Mini">
-```ts zod/v4-mini
+```ts
 const myString = z.string().check(z.refine((val) => val.length <= 255));
 ```
 </Tab>
@@ -1819,7 +1819,7 @@ const myString = z.string().refine((val) => val.length > 8, {
 ```
 </Tab>
 <Tab value="Zod Mini">
-```ts zod/v4-mini
+```ts
 const myString = z.string().check(
   z.refine((val) => val.length > 8, { error: "Too short!" })
 );
@@ -1848,7 +1848,7 @@ result.error.issues;
 ```
 </Tab>
 <Tab value="Zod Mini">
-```ts zod/v4-mini
+```ts
 const myString = z.string().check(
   z.refine((val) => val.length > 8, { error: "Too short!" }),
   z.refine((val) => val === val.toLowerCase(), { error: "Must be lowercase" })
@@ -1881,7 +1881,7 @@ result.error!.issues;
 ```
 </Tab>
 <Tab value="Zod Mini">
-```ts zod/v4-mini
+```ts
 const myString = z.string().check(
   z.refine((val) => val.length > 8, { error: "Too short!", abort: true }),
   z.refine((val) => val === val.toLowerCase(), { error: "Must be lowercase", abort: true })
@@ -2259,7 +2259,7 @@ castToString.parse(true); // => "true"
 ```
 </Tab>
 <Tab value="Zod Mini">
-```ts zod/v4-mini
+```ts
 const castToString = z.transform((val) => String(val));
 
 z.parse(castToString, "asdf"); // => "asdf"
@@ -2280,7 +2280,7 @@ type CastToString = z.infer<typeof castToString>; // string
 ```
 </Tab>
 <Tab value="Zod Mini">
-```ts zod/v4-mini
+```ts
 const castToString = z.transform((val) => String(val));
 
 type CastToString = z.infer<typeof castToString>; // string
diff --git a/packages/docs/content/basics.mdx b/packages/docs/content/basics.mdx
index 7ccac09c..994f4491 100644
--- a/packages/docs/content/basics.mdx
+++ b/packages/docs/content/basics.mdx
@@ -15,7 +15,7 @@ Before you can do anything else, you need to define a schema. For the purposes o
 <Tabs groupId="lib" items={["Zod", "Zod Mini"]}>
 <Tab value="Zod">
 ```ts
-import * as z from "zod/v4"; 
+import * as z from "zod"; 
 
 const Player = z.object({ 
   username: z.string(),
@@ -25,7 +25,7 @@ const Player = z.object({
 </Tab>
 <Tab value="Zod Mini">
 ```ts
-import * as z from "zod/v4-mini"
+import * as z from "zod/mini"
 
 const Player = z.object({ 
   username: z.string(),
@@ -35,12 +35,12 @@ const Player = z.object({
 </Tab>
 </Tabs>
 
-<Accordions>
+{/* <Accordions>
 <Accordion title='Why the "/v4" suffix?'>
   To allow time for the ecosystem the migrate, there will be a transitionary period in which Zod 4 will be published alongside Zod 3 and consumable as a subpath import:
 
   ```ts
-  import * as z from "zod/v4"
+  import * as z from "zod"
   ```
 
   During this transition window:
@@ -51,7 +51,7 @@ const Player = z.object({
 
   For a complete breakdown of the subpath versioning strategy, see the [associated writeup](https://github.com/colinhacks/zod/issues/4371).
 </Accordion>
-</Accordions>
+</Accordions> */}
 
 ## Parsing data
 
diff --git a/packages/docs/content/error-customization.mdx b/packages/docs/content/error-customization.mdx
index 39e2c5ca..0452a6e7 100644
--- a/packages/docs/content/error-customization.mdx
+++ b/packages/docs/content/error-customization.mdx
@@ -8,7 +8,7 @@ import { Tabs, Tab } from 'fumadocs-ui/components/tabs';
 
 In Zod, validation errors are surfaced as instances of the `z.core.$ZodError` class.
 
-> The `zod/v4` package uses a subclass of this called `z.ZodError` that implements some additional convenience methods.
+> The `ZodError` class in the `zod` package is a subclass that implements some additional convenience methods.
 
 Instances of `$ZodError` contain an `.issues` array. Each issue contains a human-readable `message` and additional structured metadata about the issue.
 
@@ -16,7 +16,7 @@ Instances of `$ZodError` contain an `.issues` array. Each issue contains a human
 <Tabs groupId="lib" items={["Zod", "Zod Mini"]}>
 <Tab value="Zod">
 ```ts
-import * as z from "zod/v4";
+import * as z from "zod";
 
 const result = z.string().safeParse(12); // { success: false, error: ZodError }
 result.error.issues;
@@ -32,7 +32,7 @@ result.error.issues;
 </Tab>
 <Tab value="Zod Mini">
 ```ts
-import * as z from "zod/v4-mini";
+import * as z from "zod/mini";
 
 const result = z.string().safeParse(12); // { success: false, error: z.core.$ZodError }
 result.error.issues;
@@ -292,21 +292,21 @@ const result = schema.safeParse(12, {
 
 To support internationalization of error message, Zod provides several built-in **locales**. These are exported from the `zod/v4/core` package.
 
-> **Note** — The `zod/v4` library automatically loads the `en` locale automatically. The `zod/v4-mini` sub-package does not load any locale; instead all error messages default to `Invalid input`.
+> **Note** — The regular `zod` library automatically loads the `en` locale automatically. Zod Mini does not load any locale by default; instead all error messages default to `Invalid input`.
 
 <Tabs groupId="lib" items={["Zod", "Zod Mini"]}>
 <Tab value="Zod">
 ```ts
-import * as z from "zod/v4";
-import en from "zod/v4/locales/en.js"
+import * as z from "zod";
+import en from "zod/locales/en.js"
 
 z.config(en());
 ```
 </Tab>
 <Tab value="Zod Mini">
 ```ts
-import * as z from "zod/v4-mini"
-import en from "zod/v4/locales/en.js"
+import * as z from "zod/mini"
+import en from "zod/locales/en.js"
 
 z.config(en());
 ```
@@ -316,7 +316,7 @@ z.config(en());
 To lazily load a locale, consider dynamic imports:
 
 ```ts
-import * as z from "zod/v4";
+import * as z from "zod";
 
 async function loadLocale(locale: string) {
   const { default: locale } = await import(`zod/v4/locales/${locale}.js`);
@@ -326,19 +326,19 @@ async function loadLocale(locale: string) {
 await loadLocale("fr");
 ```
 
-For convenience, all locales are exported as `z.locales` from `zod/v4`. In some bundlers, this may not be tree-shakable.
+For convenience, all locales are exported as `z.locales` from `"zod"`. In some bundlers, this may not be tree-shakable.
 
 <Tabs groupId="lib" items={["Zod", "Zod Mini"]}>
 <Tab value="Zod">
 ```ts
-import * as z from "zod/v4";
+import * as z from "zod";
 
 z.config(z.locales.en());
 ```
 </Tab>
 <Tab value="Zod Mini">
 ```ts
-import * as z from "zod/v4-mini"
+import * as z from "zod/mini"
 
 z.config(z.locales.en());
 ```
diff --git a/packages/docs/content/error-formatting.mdx b/packages/docs/content/error-formatting.mdx
index 7cc65147..6ccd48f0 100644
--- a/packages/docs/content/error-formatting.mdx
+++ b/packages/docs/content/error-formatting.mdx
@@ -10,7 +10,7 @@ Zod emphasizes _completeness_ and _correctness_ in its error reporting. In many
 Consider this simple object schema.
 
 ```ts
-import * as z from "zod/v4";
+import * as z from "zod";
 
 const schema = z.strictObject({
   username: z.string(),
@@ -170,4 +170,4 @@ The `formErrors` array contains any top-level errors (where `path` is `[]`). The
 ```ts
 flattened.fieldErrors.username; // => [ 'Invalid input: expected string, received number' ]
 flattened.fieldErrors.favoriteNumbers; // => [ 'Invalid input: expected number, received string' ]
-```
\ No newline at end of file
+```
diff --git a/packages/docs/content/index.mdx b/packages/docs/content/index.mdx
index 77a43d79..ff463d7d 100644
--- a/packages/docs/content/index.mdx
+++ b/packages/docs/content/index.mdx
@@ -84,7 +84,7 @@ import { HeroLogo } from '../components/hero-logo';
 Zod is a TypeScript-first validation library. Using Zod, you can define *schemas* you can use to validate data, from a simple `string` to a complex nested object.
 
 ```ts
-import * as z from "zod/v4";
+import * as z from "zod";
 
 const User = z.object({
   name: z.string(),
diff --git a/packages/docs/content/json-schema.mdx b/packages/docs/content/json-schema.mdx
index 7fb588f5..18f9660d 100644
--- a/packages/docs/content/json-schema.mdx
+++ b/packages/docs/content/json-schema.mdx
@@ -14,7 +14,7 @@ import { Accordion, Accordions } from 'fumadocs-ui/components/accordion';
 To convert a Zod schema to JSON Schema, use the `z.toJSONSchema()` function.
 
 ```ts
-import * as z from "zod/v4";
+import * as z from "zod";
 
 const schema = z.object({
   name: z.string(),
@@ -103,7 +103,7 @@ z.int32(); // => { type: "integer", exclusiveMinimum: ..., exclusiveMaximum: ...
 By default, `z.object()` schemas contain `additionalProperties: "false"`. This is an accurate representation of Zod's default behavior, as plain `z.object()` schema strip additional properties.
 
 ```ts
-import * as z from "zod/v4";
+import * as z from "zod";
 
 const schema = z.object({
   name: z.string(),
@@ -122,7 +122,7 @@ z.toJSONSchema(schema)
 When converting to JSON Schema in `"input"` mode, `additionalProperties` is not set. See the [`io` docs](#io) for more information.
 
 ```ts
-import * as z from "zod/v4";
+import * as z from "zod";
 
 const schema = z.object({
   name: z.string(),
@@ -261,7 +261,7 @@ In Zod, metadata is stored in registries. Zod exports a global registry `z.globa
 <Tabs groupId="lib" items={["Zod", "Zod Mini"]}>
 <Tab value="Zod">
 ```ts
-import * as z from "zod/v4";
+import * as z from "zod";
 
 // `.meta()` is a convenience method for registering a schema in `z.globalRegistry`
 const emailSchema = z.string().meta({ 
@@ -275,7 +275,7 @@ z.toJSONSchema(emailSchema);
 </Tab>
 <Tab value="Zod Mini">
 ```ts
-import * as z from "zod/v4";
+import * as z from "zod";
 
 // `.meta()` is a convenience method for registering a schema in `z.globalRegistry`
 const emailSchema = z.string().register(z.globalRegistry, { 
@@ -454,7 +454,7 @@ Passing a schema into `z.toJSONSchema()` will return a *self-contained* JSON Sch
 In other cases, you may have a set of Zod schemas you'd like to represent using multiple interlinked JSON Schemas, perhaps to write to `.json` files and serve from a web server. 
 
 ```ts
-import * as z from "zod/v4";
+import * as z from "zod";
 
 const User = z.object({
   name: z.string(),
diff --git a/packages/docs/content/library-authors.mdx b/packages/docs/content/library-authors.mdx
index e545b36d..2cf2d580 100644
--- a/packages/docs/content/library-authors.mdx
+++ b/packages/docs/content/library-authors.mdx
@@ -28,7 +28,7 @@ Any library built on top of Zod should include `"zod"` in `"peerDependencies"`.
 {
   // ...
   "peerDependencies": {
-    "zod": "^3.25.0"
+    "zod": "^3.25.0 || ^4.0.0" // the "zod/v4" subpath was added in 3.25.0
   }
 }
 ```
@@ -39,29 +39,30 @@ During development, you need to meet your own peer dependency requirement, to do
 // package.json
 {
   "peerDependencies": {
-    "zod": "^3.25.0"
+    "zod": "^3.25.0 || ^4.0.0"
   },
   "devDependencies": {
-    "zod": "^3.25.0"
+    // generally, you should develop against the latest version of Zod
+    "zod": "^3.25.0 || ^4.0.0"
   }
 }
 ```
 
 ## How to support Zod 4?
 
-To support Zod 4, update the minimum version for your `"zod"` peer dependency to `^3.25.0`. 
+To support Zod 4, update the minimum version for your `"zod"` peer dependency to `^3.25.0 || ^4.0.0`. 
 
 ```json
 // package.json
 {
   // ...
   "peerDependencies": {
-    "zod": "^3.25.0"
+    "zod": "^3.25.0 || ^4.0.0"
   }
 }
 ```
 
-Starting with `v3.25.0`, the Zod 4 core package is available at a `/v4/core` subpath. Read the [Versioning in Zod 4](https://github.com/colinhacks/zod/issues/4371) writeup for full context on this versioning approach.
+Starting with `v3.25.0`, the Zod 4 core package is available at the `"zod/v4/core"` subpath. Read the [Versioning in Zod 4](https://github.com/colinhacks/zod/issues/4371) writeup for full context on this versioning approach.
 
 ```ts
 import * as z4 from "zod/v4/core";
@@ -72,10 +73,11 @@ Import from these subpaths only. Think of them like "permalinks" to their respec
 - `"zod/v3"` for Zod 3 ✅ 
 - `"zod/v4/core"` for the Zod 4 Core package ✅
 
-Do not import from these subpaths:
+You generally shouldn't be importing from any other paths. The Zod Core library is a shared library that undergirds both Zod 4 Classic and Zod 4 Mini. It's generally a bad idea to implement any functionality that is specific to one or the other. Do not import from these subpaths:
+
+- `"zod"` — ❌ In 3.x releases, this exports Zod 3. In 4.x releases, this will export Zod 4. Use the permalinks instead. 
+- `"zod/v4"` and `"zod/v4/mini"`— ❌ These subpaths are the homes of Zod 4 Classic and Mini, respectively. If you want your library to work with both Zod and Zod Mini, you should build against the base classes defined in `"zod/v4/core"`. If you reference classes from the `"zod/v4"` module, your library will not work with Zod Mini, and vice versa. This is extremely discouraged. Use `"zod/v4/core"` instead, which exports the `$`-prefixed subclasses that are extended by Zod Classic and Zod Mini. The internals of the classic & mini subclasses are identical; they only differ in which helper methods they implement.
 
-- `"zod"` — ❌ In 3.x releases, this exports Zod 3. In a [future 4.x release](https://github.com/colinhacks/zod/issues/4371), this will export Zod 4. Use the permalinks instead. 
-- `"zod/v4"`— ❌ This subpath is the home of Zod 4 "Classic". If you reference classes from the standard module, your library will not work with Zod Mini. This is extremely discouraged. Use `"zod/v4/core"` instead, which exports the `$`-prefixed subclasses that are extended by Zod Classic and Zod Mini. The internals of the classic & mini subclasses are identical; they only differ in which helper methods they implement.
 
 ## Do I need to publish a new major version?
 
@@ -115,7 +117,7 @@ if ("_zod" in schema) {
 
 ## How to support Zod and Zod Mini simultaneously?
 
-Your library code should only import from `"zod/v4/core"`. This sub-package defines the interfaces, classes, and utilities that are shared between `zod/v4` and `zod/v4-mini`. 
+Your library code should only import from `"zod/v4/core"`. This sub-package defines the interfaces, classes, and utilities that are shared between Zod and Zod Mini. 
 
 ```ts
 // library code
@@ -129,18 +131,18 @@ export function acceptObjectSchema<T extends z4.$ZodObject>(schema: T){
 }
 ```
 
-By building against the shared base interfaces, you can reliably support both sub-packages simultaneously. This function can accept both `zod/v4` and `zod/v4-mini` schemas. 
+By building against the shared base interfaces, you can reliably support both sub-packages simultaneously. This function can accept both Zod and Zod Mini schemas. 
 
 ```ts
 // user code
 import { acceptObjectSchema } from "your-library";
 
 // Zod 4
-import * as z from "zod/v4";
+import * as z from "zod";
 acceptObjectSchema(z.object({ name: z.string() }));
 
 // Zod 4 Mini
-import * as zm from "zod/v4-mini";
+import * as zm from "zod/mini";
 acceptObjectSchema(zm.object({ name: zm.string() }))
 ```
 
diff --git a/packages/docs/content/meta.json b/packages/docs/content/meta.json
index e22f15a6..ed7d06b7 100644
--- a/packages/docs/content/meta.json
+++ b/packages/docs/content/meta.json
@@ -4,6 +4,7 @@
     "---Zod 4---",
     "v4/index",
     "v4/changelog",
+    "v4/versioning",
     "---Documentation---",
     "index",
     "basics",
diff --git a/packages/docs/content/metadata.mdx b/packages/docs/content/metadata.mdx
index 83538bab..6f331fcd 100644
--- a/packages/docs/content/metadata.mdx
+++ b/packages/docs/content/metadata.mdx
@@ -14,7 +14,7 @@ It's often useful to associate a schema with some additional *metadata* for docu
 Metadata in Zod is handled via *registries*. Registries are collections of schemas, each associated with some *strongly-typed* metadata. To create a simple registry:
 
 ```ts
-import * as z from "zod/v4";
+import * as z from "zod";
 
 const myRegistry = z.registry<{ description: string }>();
 ```
@@ -98,7 +98,7 @@ export interface GlobalMeta {
 To register some metadata in `z.globalRegistry` for a schema:
 
 ```ts
-import * as z from "zod/v4";
+import * as z from "zod";
 
 const emailSchema = z.email().register(z.globalRegistry, { 
   id: "email_address",
@@ -185,7 +185,7 @@ You've already seen a simple example of a custom registry:
 
 
 ```ts
-import * as z from "zod/v4";
+import * as z from "zod";
 
 const myRegistry = z.registry<{ description: string };>();
 ```
@@ -197,7 +197,7 @@ Let's look at some more advanced patterns.
 It's often valuable for the metadata type to reference the *inferred type* of a schema. For instance, you may want an `examples` field to contain examples of the schema's output.
 
 ```ts
-import * as z from "zod/v4";
+import * as z from "zod";
 
 type MyMeta = { examples: z.$output[] };
 const myRegistry = z.registry<MyMeta>();
@@ -213,7 +213,7 @@ The special symbol `z.$output` is a reference to the schemas inferred output typ
 Pass a second generic to `z.registry()` to constrain the schema types that can be added to a registry. This registry only accepts string schemas.
 
 ```ts
-import * as z from "zod/v4";
+import * as z from "zod";
 
 const myRegistry = z.registry<{ description: string }, z.ZodString>();
 
diff --git a/packages/docs/content/packages/core.mdx b/packages/docs/content/packages/core.mdx
index aebee1c4..df3e5a76 100644
--- a/packages/docs/content/packages/core.mdx
+++ b/packages/docs/content/packages/core.mdx
@@ -5,7 +5,7 @@ title: Zod Core
 import { Callout } from "fumadocs-ui/components/callout"
 import { Accordion, Accordions } from 'fumadocs-ui/components/accordion';
 
-This sub-package exports the core classes and utilities that are consumed by `zod/v4` and `zod/v4-mini`. It is not intended to be used directly; instead it's designed to be extended by other packages. It implements:
+This sub-package exports the core classes and utilities that are consumed by Zod and Zod Mini. It is not intended to be used directly; instead it's designed to be extended by other packages. It implements:
 
 ```ts
 import * as z from "zod/v4/core";
@@ -164,7 +164,7 @@ export type $ZodTypes =
 
 ## Internals
 
-All `zod/v4/core` subclasses only contain a single property: `_zod`. This property is an object containing the schemas *internals*. The goal is to make `zod/v4/core` as extensible and unopinionated as possible. Other libraries can "build their own Zod" on top of these classes without `zod/v4/core` cluttering up the interface. Refer to the implementations of `zod/v4` and `zod/v4-mini` for examples of how to extend these classes. 
+All `zod/v4/core` subclasses only contain a single property: `_zod`. This property is an object containing the schemas *internals*. The goal is to make `zod/v4/core` as extensible and unopinionated as possible. Other libraries can "build their own Zod" on top of these classes without `zod/v4/core` cluttering up the interface. Refer to the implementations of `zod` and `zod/mini` for examples of how to extend these classes. 
 
 The `_zod` internals property contains some notable properties:
 
@@ -379,8 +379,8 @@ The base class for all errors in Zod is `$ZodError`.
 
 > For performance reasons, `$ZodError` *does not* extend the built-in `Error` class! So using `instanceof Error` will return `false`.
 
-- The `zod/v4` package implements a subclass of `$ZodError` called `ZodError` with some additional convenience methods.
-- The `zod/v4-mini` sub-package directly uses `$ZodError`
+- The `zod` package implements a subclass of `$ZodError` called `ZodError` with some additional convenience methods.
+- The `zod/mini` sub-package directly uses `$ZodError`
 
 ```ts
 export class $ZodError<T = unknown> implements Error {
diff --git a/packages/docs/content/packages/mini.mdx b/packages/docs/content/packages/mini.mdx
index 0cb3eb97..43de8ced 100644
--- a/packages/docs/content/packages/mini.mdx
+++ b/packages/docs/content/packages/mini.mdx
@@ -9,16 +9,16 @@ import { Callout } from 'fumadocs-ui/components/callout';
   **Note** — The docs for Zod Mini are interleaved with the regular Zod docs via tabbed code blocks. This page is designed to explain why Zod Mini exists, when to use it, and some key differences from regular Zod.
 </Callout>
 
-The `zod/v4-mini` variant was introduced with the release of Zod 4. To try it:
+Zod Mini variant was introduced with the release of Zod 4. To try it:
 
 ```sh
-npm install zod@^3.25.0
+npm install zod@^4.0.0
 ```
 
 To import it:
 
 ```ts
-import * as z from "zod/v4-mini";
+import * as z from "zod/mini";
 ```
 
 Zod Mini implements the exact same functionality as `zod`, but using a *functional*, *tree-shakable* API. If you're coming from `zod`, this means you generally will use *functions* in place of methods.
@@ -35,7 +35,7 @@ const mySchema = z.nullable(z.optional(z.string()));
 
 Tree-shaking is a technique used by modern bundlers to remove unused code from the final bundle. It's also referred to as *dead-code elimination*.
 
-In regular Zod, schemas provide a range of convenience methods to perform some common operations (e.g. `.min()` on string schemas). Bundlers are generally not able to remove ("treeshake") unused method implementations from your bundle, but they are able to remove unused top-level functions. As such, the API of `zod/v4-mini` uses more functions than methods.
+In regular Zod, schemas provide a range of convenience methods to perform some common operations (e.g. `.min()` on string schemas). Bundlers are generally not able to remove ("treeshake") unused method implementations from your bundle, but they are able to remove unused top-level functions. As such, the API of Zod Mini uses more functions than methods.
 
 ```ts
 // regular Zod
@@ -108,15 +108,15 @@ Generally, the round trip time to the server (`100-200ms`) will dwarf the time r
 
 ## `ZodMiniType`
 
-All `zod/v4-mini` schemas extend the `z.ZodMiniType` base class, which in turn extends `z.core.$ZodType` from [`zod/v4/core`](/packages/core). While this class implements far fewer methods than `ZodType` in `zod`, some particularly useful methods remain.
+All Zod Mini schemas extend the `z.ZodMiniType` base class, which in turn extends `z.core.$ZodType` from [`zod/v4/core`](/packages/core). While this class implements far fewer methods than `ZodType` in `zod`, some particularly useful methods remain.
 
 
 ### `.parse`
 
-This is an obvious one. All `zod/v4-mini` schemas implement the same parsing methods as `zod`.
+This is an obvious one. All Zod Mini schemas implement the same parsing methods as `zod`.
 
 ```ts
-import * as z from "zod/v4-mini"
+import * as z from "zod/mini"
 
 const mySchema = z.string();
 
@@ -128,10 +128,10 @@ await mySchema.safeParseAsync('asdf')
 
 ### `.check()`
 
-In `zod/v4` there are dedicated methods on schema subclasses for performing common checks:
+In regular Zod there are dedicated methods on schema subclasses for performing common checks:
 
 ```ts
-import * as z from "zod/v4";
+import * as z from "zod";
 
 z.string()
   .min(5)
@@ -140,10 +140,10 @@ z.string()
   .trim()
 ```
 
-In `zod/v4-mini` such methods aren't implemented. Instead you pass these checks into schemas using the `.check()` method:
+In Zod Mini such methods aren't implemented. Instead you pass these checks into schemas using the `.check()` method:
 
 ```ts
-import * as z from "zod/v4-mini"
+import * as z from "zod/mini"
 
 z.string().check(
   z.minLength(5), 
@@ -207,7 +207,7 @@ z.string().register(myReg, { title: "My cool string schema" });
 For *branding* a schema. Refer to the [Branded types](/api#branded-types) docs for more information.
 
 ```ts
-import * as z from "zod/v4-mini"
+import * as z from "zod/mini"
 
 const USD = z.string().brand("USD");
 ```
@@ -225,12 +225,12 @@ mySchema.clone(mySchema._zod.def);
 
 ## No default locale
 
-While `zod/v4` automatically loads the English (`en`) locale, `zod/v4-mini` does not. This reduces the bundle size in scenarios where error messages are unnecessary, localized to a non-English language, or otherwise customized.
+While regular Zod automatically loads the English (`en`) locale, Zod Mini does not. This reduces the bundle size in scenarios where error messages are unnecessary, localized to a non-English language, or otherwise customized.
 
 This means, by default the `message` property of all issues will simply read `"Invalid input"`. To load the English locale:
 
 ```ts
-import * as z from "zod/v4-mini"
+import * as z from "zod/mini"
 
 z.config(z.locales.en());
 ```
diff --git a/packages/docs/content/packages/v3.mdx b/packages/docs/content/packages/v3.mdx
index 788ec15f..6c65272e 100644
--- a/packages/docs/content/packages/v3.mdx
+++ b/packages/docs/content/packages/v3.mdx
@@ -185,7 +185,7 @@ pnpm add zod@canary          # pnpm
 Creating a simple string schema
 
 ```ts
-import * as z from "zod";
+import * as z from "zod/v3";
 
 // creating a schema for strings
 const mySchema = z.string();
@@ -202,7 +202,7 @@ mySchema.safeParse(12); // => { success: false; error: ZodError }
 Creating an object schema
 
 ```ts
-import * as z from "zod";
+import * as z from "zod/v3";
 
 const User = z.object({
   username: z.string(),
@@ -220,7 +220,7 @@ type User = z.infer<typeof User>;
 ## Primitives
 
 ```ts
-import * as z from "zod";
+import * as z from "zod/v3";
 
 // primitive values
 z.string();
diff --git a/packages/docs/content/packages/zod.mdx b/packages/docs/content/packages/zod.mdx
index 679c9ac2..86d831e1 100644
--- a/packages/docs/content/packages/zod.mdx
+++ b/packages/docs/content/packages/zod.mdx
@@ -9,7 +9,7 @@ The `zod/v4` package is the "flagship" library of the Zod ecosystem. It strikes
 Zod aims to provide a schema API that maps one-to-one to TypeScript's type system.
 
 ```ts
-import * as z from "zod/v4";
+import * as z from "zod";
 
 const schema = z.object({
   name: z.string(),
@@ -30,7 +30,7 @@ z.string()
 All schemas extend the `z.ZodType` base class, which in turn extends `z.$ZodType` from [`zod/v4/core`](/packages/core). All instance of `ZodType` implement the following methods:
 
 ```ts
-import * as z from "zod/v4";
+import * as z from "zod";
 
 const mySchema = z.string();
 
diff --git a/packages/docs/content/v4/changelog.mdx b/packages/docs/content/v4/changelog.mdx
index ba1243b3..0a46e0b7 100644
--- a/packages/docs/content/v4/changelog.mdx
+++ b/packages/docs/content/v4/changelog.mdx
@@ -7,17 +7,17 @@ import { Tabs, Tab } from "fumadocs-ui/components/tabs";
 
 > This migration guide aims to list the breaking changes in Zod 4 in order of highest to lowest impact. To learn more about the performance enhancements and new features of Zod 4, read the [introductory post](/v4).
 
-To give the ecosystem time to migrate, Zod 4 will initially be published alongside Zod v3.25. To use Zod 4, upgrade to `zod@3.25.0` or later:
+{/* To give the ecosystem time to migrate, Zod 4 will initially be published alongside Zod v3.25. To use Zod 4, upgrade to `zod@3.25.0` or later: */}
 
 ```
-npm upgrade zod@^3.25.0
+npm upgrade zod@^4.0.0
 ```
 
-Zod 4 is available at the `"/v4"` subpath:
+{/* Zod 4 is available at the `"/v4"` subpath:
 
 ```ts
-import * as z from "zod/v4";
-```
+import * as z from "zod";
+``` */}
 
 <Callout>
 **Note** — Zod 3 exported a number of undocumented quasi-internal utility types and functions that are not considered part of the public API. Changes to those are not documented here.
@@ -172,7 +172,7 @@ Vitest uses special handling for `Error` subclasses that ignores enumerable prop
 The issue formats have been dramatically streamlined. 
 
 ```ts
-import * as z from "zod/v4"; // v4
+import * as z from "zod"; // v4
 
 type IssueFormats = 
   | z.core.$ZodIssueInvalidType
@@ -191,7 +191,7 @@ type IssueFormats =
 Below is the list of Zod 3 issues types and their Zod 4 equivalent:
 
 ```ts
-import * as z from "zod/v4"; // v3
+import * as z from "zod"; // v3
 
 export type IssueFormats =
   | z.ZodInvalidTypeIssue // ♻️ renamed to z.core.$ZodIssueInvalidType
@@ -694,7 +694,7 @@ The need for `z.ZodTypeAny` has been eliminated; just use `z.ZodType` instead.
 
 ### adds `z.core`
 
-Many utility functions and types have been moved to the new `zod/v4/core` sub-package, to facilitate code sharing between `zod/v4` and `zod/v4-mini`. 
+Many utility functions and types have been moved to the new `zod/v4/core` sub-package, to facilitate code sharing between Zod and Zod Mini. 
 
 ```ts
 import * as z from "zod/v4/core";
@@ -704,10 +704,10 @@ function handleError(iss: z.$ZodError) {
 }
 ```
 
-For convenience, the contents of `zod/v4/core` are also re-exported from `zod/v4` and `zod/v4-mini` under the `z.core` namespace. 
+For convenience, the contents of `zod/v4/core` are also re-exported from `zod` and `zod/mini` under the `z.core` namespace. 
 
 ```ts
-import * as z from "zod/v4";
+import * as z from "zod";
 
 function handleError(iss: z.core.$ZodError) {
   // do stuff
@@ -727,10 +727,10 @@ This doesn't affect the user-facing APIs, but it's an internal change worth high
 
 Previously both refinements and transformations lived inside a wrapper class called `ZodEffects`. That means adding either one to a schema would wrap the original schema in a `ZodEffects` instance. In Zod 4, refinements now live inside the schemas themselves. More accurately, each schema contains an array of "checks"; the concept of a "check" is new in Zod 4 and generalizes the concept of a refinement to include potentially side-effectful transforms like `z.toLowerCase()`. 
 
-This is particularly apparent in the `zod/v4-mini` API, which heavily relies on the `.check()` method to compose various validations together.
+This is particularly apparent in the Zod Mini API, which heavily relies on the `.check()` method to compose various validations together.
 
 ```ts
-import * as z from "zod/v4-mini";
+import * as z from "zod/mini";
 
 z.string().check(
   z.minLength(10),
@@ -745,7 +745,7 @@ z.string().check(
 Meanwhile, transforms have been moved into a dedicated `ZodTransform` class. This schema class represents an input transform; in fact, you can actually define standalone transformations now:
 
 ```ts
-import * as z from "zod/v4";
+import * as z from "zod";
 
 const schema = z.transform(input => String(input));
 
diff --git a/packages/docs/content/v4/index.mdx b/packages/docs/content/v4/index.mdx
index 439d8a36..25b5d301 100644
--- a/packages/docs/content/v4/index.mdx
+++ b/packages/docs/content/v4/index.mdx
@@ -14,21 +14,20 @@ Huge thanks to [Clerk](https://go.clerk.com/zod-clerk), who supported my work on
 
 ## Versioning 
 
-To simplify the migration process both for users and Zod's ecosystem of associated libraries, Zod 4 will initially published alongside Zod 3 as part of the `zod@3.25` release. Despite the version number, it is considered stable and ready for production use.
+{/* <Callout> 
+  **Update** — `zod@4.0.0` has now been published to npm. To upgrad
+</Callout> */}
+
+{/* To simplify the migration process both for users and Zod's ecosystem of associated libraries, Zod 4 will initially published alongside Zod 3 as part of the `zod@3.25` release. Despite the version number, it is considered stable and ready for production use. */}
 
 To upgrade: 
 
 ```
-npm upgrade zod@^3.25.0
+npm upgrade zod@^4.0.0
 ```
 
-Then import Zod 4 from the `"/v4"` subpath:
-
-```ts
-import * as z from "zod/v4";
-```
 
-Down the road, when there's broad support for Zod 4, we'll publish `zod@4.0.0` on npm. At this point, Zod 4 will be exported from the package root (`"zod"`). The `"zod/v4"` subpath will remain available. For a detailed writeup on the reasons for this versioning scheme, refer to [this issue](https://github.com/colinhacks/zod/issues/4371). 
+{/* Down the road, when there's broad support for Zod 4, we'll publish `zod@4.0.0` on npm. At this point, Zod 4 will be exported from the package root (`"zod"`). The `"zod/v4"` subpath will remain available. For a detailed writeup on the reasons for this versioning scheme, refer to [this issue](https://github.com/colinhacks/zod/issues/4371).  */}
 
 For a complete list of breaking changes, refer to the [Migration guide](/v4/changelog). This post focuses on new features & enhancements.
 
@@ -120,7 +119,7 @@ summary for z.object() safeParse
 Consider the following simple file:
 
 ```ts
-import * as z from "zod/v4";
+import * as z from "zod";
 
 export const A = z.object({
   a: z.string(),
@@ -152,7 +151,7 @@ $ pnpm bench object-with-extend
 More importantly, Zod 4 has redesigned and simplified the generics of `ZodObject` and other schema classes to avoid some pernicious "instantiation explosions". For instance, chaining `.extend()` and `.omit()` repeatedly—something that previously caused compiler issues:
 
 ```ts
-import * as z from "zod/v4";
+import * as z from "zod";
 
 export const a = z.object({
   a: z.string(),
@@ -266,7 +265,7 @@ In Zod 3, this took `4000ms` to compile; and adding additional calls to `.extend
 Consider the following simple script.
 
 ```ts
-import * as z from "zod/v4";
+import * as z from "zod";
 
 const schema = z.boolean();
 
@@ -277,25 +276,25 @@ It's about as simple as it gets when it comes to validation. That's intentional;
 
 | Package | Bundle (gzip) |
 |---------|--------------|
-| `zod/v3`   | `12.47kb`      |
-| `zod/v4`   | `5.36kb`       |
+| Zod 3   | `12.47kb`      |
+| Zod 4   | `5.36kb`       |
 
 The core bundle is ~57% smaller in Zod 4 (2.3x). That's good! But we can do a lot better.
 
 ## Introducing Zod Mini
 
-Zod's method-heavy API is fundamentally difficult to tree-shake. Even our simple `z.boolean()` script pulls in the implementations of a bunch of methods we didn't use, like `.optional()`, `.array()`, etc. Writing slimmer implementations can only get you so far. That's where `zod/v4-mini` comes in. 
+Zod's method-heavy API is fundamentally difficult to tree-shake. Even our simple `z.boolean()` script pulls in the implementations of a bunch of methods we didn't use, like `.optional()`, `.array()`, etc. Writing slimmer implementations can only get you so far. That's where Zod Mini comes in. 
 
 ```sh
 npm install zod@^3.25.0
 ```
 
-It's a Zod variant with a functional, tree-shakable API that corresponds one-to-one with `zod`. Where Zod uses methods, `zod/v4-mini` generally uses wrapper functions:
+It's a Zod variant with a functional, tree-shakable API that corresponds one-to-one with `zod`. Where Zod uses methods, Zod Mini generally uses wrapper functions:
 
 <Tabs groupId="lib" items={[ "Zod Mini", "Zod"]}>
 <Tab value="Zod Mini">
 ```ts
-import * as z from "zod/v4-mini";
+import * as z from "zod/mini";
 
 z.optional(z.string());
 
@@ -306,7 +305,7 @@ z.extend(z.object({ /* ... */ }), { age: z.number() });
 </Tab>
 <Tab value="Zod">
 ```ts
-import * as z from "zod/v4";
+import * as z from "zod";
 
 z.string().optional();
 
@@ -317,10 +316,10 @@ z.object({ /* ... */ }).extend({ age: z.number() });
 </Tab>
 </Tabs>
 
-Not all methods are gone! The parsing methods are identical in `zod/v4` and `zod/v4-mini`.
+Not all methods are gone! The parsing methods are identical in Zod and Zod Mini:
 
 ```ts
-import * as z from "zod/v4-mini";
+import * as z from "zod/mini";
 
 z.string().parse("asdf");
 z.string().safeParse("asdf");
@@ -333,7 +332,7 @@ There's also a general-purpose `.check()` method used to add refinements.
 <Tabs groupId="lib" items={[ "Zod Mini", "Zod"]}>
 <Tab value="Zod Mini">
 ```ts
-import * as z from "zod/v4-mini";
+import * as z from "zod/mini";
 
 z.array(z.number()).check(
   z.minLength(5), 
@@ -344,7 +343,7 @@ z.array(z.number()).check(
 </Tab>
 <Tab value="Zod">
 ```ts
-import * as z from "zod/v4";
+import * as z from "zod";
 
 z.array(z.number())
   .min(5)
@@ -354,10 +353,10 @@ z.array(z.number())
 </Tab>
 </Tabs>
 
-The following top-level refinements are available in `zod/v4-mini`. It should be fairly self-explanatory which methods they correspond to.
+The following top-level refinements are available in Zod Mini. It should be fairly self-explanatory which Zod methods they correspond to.
 
 ```ts
-import * as z from "zod/v4-mini";
+import * as z from "zod/mini";
 
 // custom checks
 z.refine();
@@ -396,14 +395,14 @@ z.toUpperCase();
 ```
 
 
-This more functional API makes it easier for bundlers to tree-shake the APIs you don't use. While `zod/v4` is still recommended for the majority of use cases, any projects with uncommonly strict bundle size constraints should consider `zod/v4-mini`.  
+This more functional API makes it easier for bundlers to tree-shake the APIs you don't use. While regular Zod is still recommended for the majority of use cases, any projects with uncommonly strict bundle size constraints should consider Zod Mini.  
 
 ### 6.6x reduction in core bundle size
 
-Here's the script from above, updated to use `"zod/v4-mini"` instead of `"zod"`.
+Here's the script from above, updated to use `"zod/mini"` instead of `"zod"`.
 
 ```ts
-import * as z from "zod/v4-mini";
+import * as z from "zod/mini";
 
 const schema = z.boolean();
 schema.parse(false);
@@ -413,18 +412,18 @@ When we build this with `rollup`, the gzipped bundle size is `1.88kb`. That's an
 
 | Package   | Bundle (gzip) |
 |-----------|--------------|
-| `zod/v3`     |  `12.47kb`      |
-| `zod/v4`     | `5.36kb`       |
-| `zod/v4-mini` | `1.88kb`       |
+| Zod 3     |  `12.47kb`      |
+| Zod 4 (regular)     | `5.36kb`       |
+| Zod 4 (mini) | `1.88kb`       |
 
-Learn more on the dedicated [`zod/v4-mini`](/packages/mini) docs page. Complete API details are mixed into existing documentation pages; code blocks contain separate tabs for `"Zod"` and `"Zod Mini"` wherever their APIs diverge.
+Learn more on the dedicated [`zod/mini`](/packages/mini) docs page. Complete API details are mixed into existing documentation pages; code blocks contain separate tabs for `"Zod"` and `"Zod Mini"` wherever their APIs diverge.
 
 ## Metadata
 
 Zod 4 introduces a new system for adding strongly-typed metadata to your schemas. Metadata isn't stored inside the schema itself; instead it's stored in a "schema registry" that associates a schema with some typed metadata. To create a registry with `z.registry()`:
 
 ```ts
-import * as z from "zod/v4";
+import * as z from "zod";
 
 const myRegistry = z.registry<{ title: string; description: string }>();
 ```
@@ -494,7 +493,7 @@ z.string().meta({ description: "An email address" });
 Zod 4 introduces first-party JSON Schema conversion via `z.toJSONSchema()`.
 
 ```ts
-import * as z from "zod/v4";
+import * as z from "zod";
 
 const mySchema = z.object({name: z.string(), points: z.number()});
 
@@ -592,7 +591,7 @@ fileSchema.mime(["image/png"]); // MIME type
 Zod 4 introduces a new `locales` API for globally translating error messages into different languages. 
 
 ```ts
-import * as z from "zod/v4";
+import * as z from "zod";
 
 // configure English locale (default)
 z.config(z.locales.en());
@@ -914,21 +913,21 @@ z.number().overwrite(val => val ** 2).max(100);
 
 ## An extensible foundation: `zod/v4/core`
 
-While this will not be relevant to the majority of Zod users, it's worth highlighting. The addition of `zod/v4-mini` necessitated the creation of a shared sub-package `zod/v4/core` which contains the core functionality shared between `zod/v4` and `zod/v4-mini`. 
+While this will not be relevant to the majority of Zod users, it's worth highlighting. The addition of Zod Mini necessitated the creation of a shared sub-package `zod/v4/core` which contains the core functionality shared between Zod and Zod Mini. 
 
 I was resistant to this at first, but now I see it as one of Zod 4's most important features. It lets Zod level up from a simple library to a fast validation "substrate" that can be sprinkled into other libraries.
 
-If you're building a schema library, refer to the implementations of `zod/v4` and `zod/v4-mini` to see how to build on top of the foundation `zod/v4/core` provides. Don't hesitate to get in touch in GitHub discussions or via [X](https://x.com/colinhacks)/[Bluesky](https://bsky.app/profile/colinhacks.com) for help or feedback.
+If you're building a schema library, refer to the implementations of Zod and Zod Mini to see how to build on top of the foundation `zod/v4/core` provides. Don't hesitate to get in touch in GitHub discussions or via [X](https://x.com/colinhacks)/[Bluesky](https://bsky.app/profile/colinhacks.com) for help or feedback.
 
 
 ## Wrapping up
 
-I'm planning to write up a series of additional posts explaining the design process behind some major features like `zod/v4-mini`. I'll update this section as those get posted. 
+I'm planning to write up a series of additional posts explaining the design process behind some major features like Zod Mini. I'll update this section as those get posted. 
 
 For library authors, there is now a dedicated [For library authors](/library-authors) guide that describes the best practices for building on top of Zod. It answers common questions about how to support Zod 3 & Zod 4 (including Mini) simultaneously.
 
 ```sh
-pnpm upgrade zod@^3.25.0
+pnpm upgrade zod@latest
 ```
 
 Happy parsing!<br/>
diff --git a/packages/docs/content/v4/versioning.mdx b/packages/docs/content/v4/versioning.mdx
new file mode 100644
index 00000000..e2c686e3
--- /dev/null
+++ b/packages/docs/content/v4/versioning.mdx
@@ -0,0 +1,108 @@
+---
+title: Versioning
+---
+
+
+import { Callout } from "fumadocs-ui/components/callout";
+
+
+### **Update — July 8th, 2025**
+
+`zod@4.0.0` has been published to `npm`. The package root (`"zod"`) now exports Zod 4. All other subpaths have not changed and will remain available forever.
+
+To upgrade to Zod 4:
+
+```
+npm upgrade zod@^4.0.0
+```
+
+If you are using Zod 4, your existing imports (`"zod/v4"` and `"zod/v4-mini"`) will continue to work forever. However, after upgrading, you can *optionally* rewrite your imports as follows:
+
+
+|                | Before   | After     |
+|----------------|-----------------|-------------|
+| Zod 4          | `"zod/v4"`      | `"zod"`     |
+| Zod 4 Mini     | `"zod/v4-mini"` | `"zod/mini"`|
+| Zod 3          | `"zod"`         | `"zod/v3"`  |
+
+
+**Library authors** — if you've already implemented Zod 4 support according to the best practices outlined in the [Library authors](/library-authors) guide, bump your peer dependency to include `zod@^4.0.0`:
+
+```json
+// package.json
+{
+  "peerDependencies": {
+    "zod": "^3.25.0 || ^4.0.0"
+  }
+}
+```
+
+*There should be no other code changes necessary.* No code changes were made between the latest `3.25.x` release and `4.0.0`. This does not require a major version bump.
+
+<details>
+<summary><strong>Some notes on subpath versioning</strong></summary>
+
+Ultimately, the subpath versioning scheme was a necessary evil to force the ecosystem to upgrade in a non-breaking way. If I'd published `zod@4.0.0` out of the gate, most libraries would have naively bumped their peer dependencies, forcing a "version bump avalanche" across the ecosystem.
+
+As it stands, there is now [broad support](https://x.com/colinhacks/status/1932323805705482339) for Zod 4 across the ecosystem. No migration process is totally painless, but it seems like the "version avalanche" I'd feared didn't happen. By and large, libraries have been able to support Zod 3 and Zod 4 simultaneously: Hono, LangChain, React Hook Form, etc. Several ecosystem maintainers reached out to me specifically to indicate how convenient it was to incrementally add support for Zod 4 (something that would typically require a major version bump). Long story short: this approach worked great! Few other libraries are subject to the same constraints as Zod, but I strongly encourage other libraries with large associated ecosystems to consider a similar approach.
+</details>
+
+## Versioning in Zod 4
+
+This is a writeup of Zod 4's approach to versioning, with the goal of making it easier for users and Zod's ecosystem of associated libraries to migrate to Zod 4.
+
+The general approach:
+
+- Zod 4 will not initially be published as `zod@4.0.0` on npm. Instead it will be exported at a subpath (`"zod/v4"`) alongside `zod@3.25.0` 
+- Despite this, Zod 4 is considered stable and production-ready
+- Zod 3 will continue to be exported from the package root (`"zod"`) as well as a new subpath `"zod/v3"`. It will continue to receive bug fixes & stability improvements.
+ 
+> This approach is analogous to how Golang handles major version changes: https://go.dev/doc/modules/major-version
+
+Sometime later:
+
+- The package root (`"zod"`) will switch over from exporting Zod 3 to Zod 4
+- At this point `zod@4.0.0` will get published to npm
+- The `"zod/v4"` subpath will remain available forever
+
+## Why? 
+
+Zod occupies a unique place in the ecosystem. Many libraries/frameworks in the ecosystem accept user-defined Zod schemas. This means their user-facing API is strongly coupled to Zod and its various classes/interfaces/utilities. For these libraries/frameworks, a breaking change to Zod necessarily causes a breaking change for their users. A Zod 3 `ZodType` is not assignable to a Zod 4 `ZodType`.
+
+
+### Why can't libraries just support v3 and v4 simultaneously? 
+
+Unfortunately the limitations of peerDependencies (and inconsistencies between package managers) make it extremely difficult to elegantly support two major versions of one library simultaneously.
+
+If I naively published `zod@4.0.0` to npm, the vast majority of the libraries in Zod's ecosystem would need to publish a new major version to properly support Zod 4, include some high-profile libraries like the AI SDK. It would trigger a "version bump avalanche" across the ecosystem and generally create a huge amount of frustration and work. 
+  
+With subpath versioning, we solve this problem. it provides a straightforward way for libraries to support Zod 3 and Zod 4 (including Zod Mini) simultaneously. They can continue defining a single peerDependency on `"zod"`; no need for more arcane solutions like npm aliases, optional peer dependencies, a `"zod-compat"` package, or other such hacks.
+
+Libraries will need to bump the minimum version of their `"zod"` peer dependency to `zod@^3.25.0`. They can then reference both Zod 3 and Zod 4 in their implementation:
+
+  ```ts 
+  import * as z3 from "zod/v3"
+  import * as z4 from "zod/v4"
+  ```
+
+Later, once there's broad support for v4, we'll bump the major version on `npm` and start exporting Zod 4 from the package root, completing the transition. (This has now happened—see the note at the top of this page.)
+
+As long as libraries are importing exclusively from the associated subpaths (not the root), their implementations will continue to work across the major version bump without code changes.
+
+While it may seem unorthodox (at least for people who don't use Go!), this is the only approach I'm aware of that enables a clean, incremental migration path for both Zod's users and the libraries in the broader ecosystem.
+
+---
+
+A deeper dive into why peer dependencies don't work in this situation.
+
+Imagine you're a library trying to build a function `acceptSchema` that accepts a Zod schema. You want to be able to accept Zod 3 or Zod 4 schemas.  In this hypothetical, I'm imagine Zod 4 was published as `zod@4` on npm, no subpaths. Here are your options:
+
+1. Install both zod@3 and zod@4 as `dependencies` simultaneously using npm aliases. This works but you end up including your own copies of both Zod 3 and Zod 4. You have no guarantee that your user's Zod schemas are instances of the same z.ZodType class you're pulling from dependencies (`instanceof` checks will probably fail). 
+
+2. Use a peer dependency that spans multiple major versions:  `"zod@>=3.0.0"` …but when developing a library you’d still need to pick a version to develop against. Usually you'd install this as a dev dependency. The onus is on you to painstakingly ensure your code works, character-for-character,  across both versions. This is impossible in the case of Zod 3 & Zod 4 because a number of very fundamental classes have simplified/different generics.
+
+3. Optional peer dependencies. i just couldn't find a straight answer about how to reliably determine which peer dep is installed at runtime across all platforms. Many answers online will say "use dynamic imports in a try/catch to check it a package exists". Those folks are assuming you're on the backend because no frontend bundlers have no affordance for this. They'll fail when you try to bundle a dependency that isn't installed. Obviuosly it doesn't matter if you're inside a try/catch during a build step. Also: since we're talking about multiple versions of the same library, you'd need to use npm aliases to differentiate the two versions in your `package.json`. Versions of npm as recent as v10 cannot handle the combination of peer dependencies + npm aliases.
+
+4. `zod-compat`. This extremely hand-wavy solution you see online is "define interfaces for each version that represents some basic functionality". Basically some utility types libraries can use to approximate the real deal. This is error prone, a ton of work, needs to be kept synchronized with the real implementations, and ultimately libraries are developing against a shadow version of your library that probably lacks detail. It also only works for types: if a library depends on any runtime code in Zod it falls apart.
+
+Hence, subpaths.
diff --git a/packages/resolution/src/index.cts b/packages/resolution/src/index.cts
index 6d316630..41d1ce0a 100644
--- a/packages/resolution/src/index.cts
+++ b/packages/resolution/src/index.cts
@@ -1,9 +1,10 @@
 import * as z1 from "zod";
 import z2 from "zod";
 import { z as z3 } from "zod";
+import * as z7 from "zod/mini";
 import * as z5 from "zod/v3";
 import * as z4 from "zod/v4";
-import * as z7 from "zod/v4-mini";
+import * as z8 from "zod/v4-mini";
 import fr from "zod/v4/locales/fr.cjs";
 import * as z6 from "zod/v4/mini";
 
@@ -14,6 +15,7 @@ console.log(z4.string().parse("Hello, world!"));
 console.log(z5.string().parse("Hello, world!"));
 console.log(z6.string().parse("Hello, world!"));
 console.log(z7.string().parse("Hello, world!"));
+console.log(z8.string().parse("Hello, world!"));
 
 z4.config(fr());
 const schema = z4.object({
diff --git a/packages/resolution/src/index.mts b/packages/resolution/src/index.mts
index 5181a29b..d679740f 100644
--- a/packages/resolution/src/index.mts
+++ b/packages/resolution/src/index.mts
@@ -1,9 +1,10 @@
 import * as z1 from "zod";
 import z2 from "zod";
 import { z as z3 } from "zod";
+import * as z7 from "zod/mini";
 import * as z5 from "zod/v3";
 import * as z4 from "zod/v4";
-import * as z7 from "zod/v4-mini";
+import * as z8 from "zod/v4-mini";
 import fr from "zod/v4/locales/fr.js";
 import * as z6 from "zod/v4/mini";
 
@@ -14,6 +15,7 @@ console.log(z4.string().parse("Hello, world!"));
 console.log(z5.string().parse("Hello, world!"));
 console.log(z6.string().parse("Hello, world!"));
 console.log(z7.string().parse("Hello, world!"));
+console.log(z8.string().parse("Hello, world!"));
 
 z4.config(fr());
 const schema = z4.object({
diff --git a/packages/resolution/src/index.ts b/packages/resolution/src/index.ts
index acaae158..0eff2a83 100644
--- a/packages/resolution/src/index.ts
+++ b/packages/resolution/src/index.ts
@@ -1,9 +1,10 @@
 import * as z1 from "zod";
 import z2 from "zod";
 import { z as z3 } from "zod";
+import * as z7 from "zod/mini";
 import * as z5 from "zod/v3";
 import * as z4 from "zod/v4";
-import * as z7 from "zod/v4-mini";
+import * as z8 from "zod/v4-mini";
 import fr from "zod/v4/locales/fr.js";
 import * as z6 from "zod/v4/mini";
 
@@ -14,6 +15,7 @@ console.log(z4.string().parse("Hello, world!"));
 console.log(z5.string().parse("Hello, world!"));
 console.log(z6.string().parse("Hello, world!"));
 console.log(z7.string().parse("Hello, world!"));
+console.log(z8.string().parse("Hello, world!"));
 
 z4.config(fr());
 const schema = z4.object({
diff --git a/packages/treeshake/example-mini.ts b/packages/treeshake/example-mini.ts
index 868506ca..8701ffbb 100644
--- a/packages/treeshake/example-mini.ts
+++ b/packages/treeshake/example-mini.ts
@@ -1,4 +1,4 @@
-import * as z from "zod/v4-mini"
+import * as z from "zod/mini"
  
 z.string().check(
   z.minLength(5), 
diff --git a/packages/treeshake/zod-locales.ts b/packages/treeshake/zod-locales.ts
index abfecaec..6da2598c 100644
--- a/packages/treeshake/zod-locales.ts
+++ b/packages/treeshake/zod-locales.ts
@@ -1,4 +1,4 @@
-import * as z from "zod/v4-mini"
+import * as z from "zod/mini"
 
 z.config(z.locales.en());
 
diff --git a/packages/treeshake/zod-mini-boolean.ts b/packages/treeshake/zod-mini-boolean.ts
index 53dec35a..b1e2dc80 100644
--- a/packages/treeshake/zod-mini-boolean.ts
+++ b/packages/treeshake/zod-mini-boolean.ts
@@ -1,4 +1,4 @@
-import * as z from "zod/v4-mini"
+import * as z from "zod/mini"
 
 const schema = z.boolean();
 console.log(schema.parse(true));
diff --git a/packages/treeshake/zod-mini-full.ts b/packages/treeshake/zod-mini-full.ts
index 92e2fb69..6a7e786d 100644
--- a/packages/treeshake/zod-mini-full.ts
+++ b/packages/treeshake/zod-mini-full.ts
@@ -1,4 +1,4 @@
-import * as z from "zod/v4-mini"
+import * as z from "zod/mini"
 
 export const schema = z.object({
   name: z.string(),
diff --git a/packages/treeshake/zod-mini-object.ts b/packages/treeshake/zod-mini-object.ts
index 97f86848..f1c1fb93 100644
--- a/packages/treeshake/zod-mini-object.ts
+++ b/packages/treeshake/zod-mini-object.ts
@@ -1,4 +1,4 @@
-import * as z from "zod/v4-mini"
+import * as z from "zod/mini"
 
 const schema = z.object({ a: z.string(), b: z.number(), c: z.boolean() });
 
diff --git a/packages/treeshake/zod-mini-string.ts b/packages/treeshake/zod-mini-string.ts
index fdc0872a..1f2dff93 100644
--- a/packages/treeshake/zod-mini-string.ts
+++ b/packages/treeshake/zod-mini-string.ts
@@ -1,4 +1,4 @@
-import * as z from "zod/v4-mini"
+import * as z from "zod/mini"
 
 const schema = z.string().check(z.minLength(5));
 
diff --git a/packages/zod/README.md b/packages/zod/README.md
index 5ffbaa84..40668ac9 100644
--- a/packages/zod/README.md
+++ b/packages/zod/README.md
@@ -58,7 +58,7 @@
 Zod is a TypeScript-first validation library. Define a schema and parse some data with it. You'll get back a strongly typed, validated result.
 
 ```ts
-import * as z from "zod/v4";
+import * as z from "zod";
 
 const User = z.object({
   name: z.string(),
@@ -104,7 +104,7 @@ npm install zod
 Before you can do anything else, you need to define a schema. For the purposes of this guide, we'll use a simple object schema.
 
 ```ts
-import * as z from "zod/v4";
+import * as z from "zod";
 
 const Player = z.object({
   username: z.string(),
diff --git a/packages/zod/jsr.json b/packages/zod/jsr.json
index bca902f3..0b25db65 100644
--- a/packages/zod/jsr.json
+++ b/packages/zod/jsr.json
@@ -1,12 +1,19 @@
 {
   "name": "@zod/zod",
-  "version": "4.0.0-alpha.0",
+  "version": "4.0.0",
   "exports": {
+    "./package.json": "./package.json",
     ".": "./src/index.ts",
-    "./package.json": "./package.json"
+    "./mini": "./src/v4/mini/index.ts",
+    "./v3": "./src/v3/index.ts",
+    "./v4": "./src/v4/index.ts",
+    "./v4-mini": "./src/v4-mini/index.ts",
+    "./v4/mini": "./src/v4/mini/index.ts",
+    "./v4/core": "./src/v4/core/index.ts",
+    "./v4/locales": "./src/v4/locales/index.ts"
   },
   "publish": {
     "include": ["src", "LICENSE", "package.json", "README.md"],
-    "exclude": ["tests", "dist"]
+    "exclude": ["**/tests/**", "dist"]
   }
 }
diff --git a/packages/zod/package.json b/packages/zod/package.json
index eedcfb0f..87ec8e23 100644
--- a/packages/zod/package.json
+++ b/packages/zod/package.json
@@ -1,6 +1,6 @@
 {
   "name": "zod",
-  "version": "3.25.76",
+  "version": "4.0.0",
   "type": "module",
   "author": "Colin McDonnell <zod@colinhacks.com>",
   "description": "TypeScript-first schema declaration and validation library with static type inference",
@@ -31,6 +31,8 @@
     "exports": {
       "./package.json": "./package.json",
       ".": "./src/index.ts",
+      "./mini": "./src/mini/index.ts",
+      "./locales": "./src/locales/index.ts",
       "./v3": "./src/v3/index.ts",
       "./v4": "./src/v4/index.ts",
       "./v4-mini": "./src/v4-mini/index.ts",
@@ -51,6 +53,18 @@
       "import": "./index.js",
       "require": "./index.cjs"
     },
+    "./mini": {
+      "@zod/source": "./src/mini/index.ts",
+      "types": "./mini/index.d.cts",
+      "import": "./mini/index.js",
+      "require": "./mini/index.cjs"
+    },
+    "./locales": {
+      "@zod/source": "./src/locales/index.ts",
+      "types": "./locales/index.d.cts",
+      "import": "./locales/index.js",
+      "require": "./locales/index.cjs"
+    },
     "./v3": {
       "@zod/source": "./src/v3/index.ts",
       "types": "./v3/index.d.cts",
@@ -112,7 +126,6 @@
     "postbuild": "pnpm biome check --write .",
     "test:watch": "pnpm vitest",
     "test": "pnpm vitest run",
-    "bump:beta": "pnpm version \"v$(pnpm pkg get version | jq -r)-beta.$(date +%Y%m%dT%H%M%S)\"",
-    "pub:beta": "pnpm bump:beta && pnpm publish --tag next --publish-branch v4 --no-git-checks --dry-run"
+    "prepublishOnly": "tsx ../../scripts/check-versions.ts"
   }
 }
diff --git a/packages/zod/src/index.ts b/packages/zod/src/index.ts
index bf6f1643..b0bbaefb 100644
--- a/packages/zod/src/index.ts
+++ b/packages/zod/src/index.ts
@@ -1,4 +1,4 @@
-import * as z from "./v3/external.js";
-export * from "./v3/external.js";
+import * as z from "./v4/classic/external.js";
+export * from "./v4/classic/external.js";
 export { z };
 export default z;
diff --git a/packages/zod/src/locales/index.ts b/packages/zod/src/locales/index.ts
new file mode 100644
index 00000000..e6e7dd67
--- /dev/null
+++ b/packages/zod/src/locales/index.ts
@@ -0,0 +1,1 @@
+export * from "../v4/locales/index.js";
diff --git a/packages/zod/src/mini/index.ts b/packages/zod/src/mini/index.ts
new file mode 100644
index 00000000..87b293a4
--- /dev/null
+++ b/packages/zod/src/mini/index.ts
@@ -0,0 +1,1 @@
+export * from "../v4/mini/index.js";
diff --git a/packages/zod/src/v4/core/doc.ts b/packages/zod/src/v4/core/doc.ts
index 9591fe7a..cec43e67 100644
--- a/packages/zod/src/v4/core/doc.ts
+++ b/packages/zod/src/v4/core/doc.ts
@@ -33,12 +33,12 @@ export class Doc {
     }
   }
 
-  compile() {
+  compile(): any {
     const F = Function;
     const args = this?.args;
     const content = this?.content ?? [``];
     const lines = [...content.map((x) => `  ${x}`)];
     // console.log(lines.join("\n"));
-    return new F(...args, lines.join("\n"));
+    return new F(...args, lines.join("\n")) as any;
   }
 }
diff --git a/packages/zod/src/v4/core/regexes.ts b/packages/zod/src/v4/core/regexes.ts
index bfcb6a0e..07c4e2bd 100644
--- a/packages/zod/src/v4/core/regexes.ts
+++ b/packages/zod/src/v4/core/regexes.ts
@@ -49,7 +49,7 @@ export const browserEmail: RegExp =
   /^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$/;
 // from https://thekevinscott.com/emojis-in-javascript/#writing-a-regular-expression
 
-export const _emoji = `^(\\p{Extended_Pictographic}|\\p{Emoji_Component})+$`;
+const _emoji: string = `^(\\p{Extended_Pictographic}|\\p{Emoji_Component})+$`;
 export function emoji(): RegExp {
   return new RegExp(_emoji, "u");
 }
diff --git a/packages/zod/tsconfig.json b/packages/zod/tsconfig.json
index 2e6932b3..4134b751 100644
--- a/packages/zod/tsconfig.json
+++ b/packages/zod/tsconfig.json
@@ -4,5 +4,5 @@
     "module": "nodenext",
     "customConditions": ["@zod/source"],
   },
-  "include": ["**/*.ts"]
+  "include": ["**/*.ts", "../../scripts/check-versions.ts"]
 }
diff --git a/play.ts b/play.ts
index 9a1ba647..4564a8e8 100644
--- a/play.ts
+++ b/play.ts
@@ -1,19 +1,3 @@
-import * as z from "zod/v4";
+import * as z from "zod";
 
-// ID is branded. Brand under the hood is an obj structur {...}
-const Id = z.string().brand("Id");
-const Obj = z.object({
-  id: Id,
-  name: z.string(),
-});
-
-const result = Obj.safeParse({});
-if (result.error) {
-  const tree = z.treeifyError(result.error);
-  // Compare the two types
-  // name is looked up as primitive string
-  // id is looked up as object with its properties, ending up with the properties of string
-  // this is because of the brand obj intersection with string
-  type TreeId = NonNullable<(typeof tree)["properties"]>["id"];
-  type TreeName = NonNullable<(typeof tree)["properties"]>["name"];
-}
+z;
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 0b78004b..f6b2bc00 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -20,6 +20,9 @@ importers:
       '@types/node':
         specifier: ^20.17.30
         version: 20.17.30
+      '@types/semver':
+        specifier: ^7.7.0
+        version: 7.7.0
       '@web-std/file':
         specifier: ^3.0.3
         version: 3.0.3
@@ -102,8 +105,8 @@ importers:
         specifier: npm:zod@~3.24.0
         version: zod@3.24.3
       zshy:
-        specifier: ^0.0.12
-        version: 0.0.12(typescript@5.5.4)
+        specifier: ^0.0.13
+        version: 0.0.13(typescript@5.5.4)
 
   packages/bench:
     devDependencies:
@@ -127,7 +130,7 @@ importers:
     dependencies:
       '@inkeep/cxkit-react':
         specifier: ^0.5.80
-        version: 0.5.80(@types/react-dom@19.0.4(@types/react@19.0.11))(@types/react@19.0.11)(encoding@0.1.13)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(zod@packages+zod)
+        version: 0.5.80(@types/react-dom@19.0.4(@types/react@19.0.11))(@types/react@19.0.11)(encoding@0.1.13)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(zod@3.25.68)
       '@radix-ui/react-accordion':
         specifier: ^1.2.4
         version: 1.2.4(@types/react-dom@19.0.4(@types/react@19.0.11))(@types/react@19.0.11)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
@@ -2657,6 +2660,9 @@ packages:
   '@types/resolve@1.20.2':
     resolution: {integrity: sha512-60BCwRFOZCQhDncwQdxxeOEEkbc5dIMccYLwbxsS4TUNeVECQ/pBJ0j09mrHOl/JJvpRPGwO9SvE4nR2Nb/a4Q==}
 
+  '@types/semver@7.7.0':
+    resolution: {integrity: sha512-k107IF4+Xr7UHjwDc7Cfd6PRQfbdkiRabXGRjo07b4WyPahFBZCZ1sE+BNxYIJPPg73UkfOsVOLwqVc/6ETrIA==}
+
   '@types/unist@2.0.11':
     resolution: {integrity: sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==}
 
@@ -5755,8 +5761,8 @@ packages:
   zod@4.0.0-beta.20250420T053007:
     resolution: {integrity: sha512-5pp8Q0PNDaNcUptGiBE9akyioJh3RJpagIxrLtAVMR9IxwcSZiOsJD/1/98CyhItdTlI2H91MfhhLzRlU+fifA==}
 
-  zshy@0.0.12:
-    resolution: {integrity: sha512-5txlxh4PoZIU4zRdXbhreeY/bnEgUvmeOXXM6TKH4FdqKDTlyk2H99SF9RmB+/U4ciAGXeXkD79Z/bQrN++pCQ==}
+  zshy@0.0.13:
+    resolution: {integrity: sha512-OyEo2NoCbCtpY9Pr5PO/7Nna6RjDQnp8k3t2df1UKRe4rMH4A0D4KqDteDBosdQ4pYiTNW927CXZrflcEFNXpQ==}
     hasBin: true
     peerDependencies:
       typescript: '>5.5.0'
@@ -6232,7 +6238,7 @@ snapshots:
 
   '@inkeep/cxkit-color-mode@0.5.80': {}
 
-  '@inkeep/cxkit-primitives@0.5.80(@types/react-dom@19.0.4(@types/react@19.0.11))(@types/react@19.0.11)(encoding@0.1.13)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(zod@packages+zod)':
+  '@inkeep/cxkit-primitives@0.5.80(@types/react-dom@19.0.4(@types/react@19.0.11))(@types/react@19.0.11)(encoding@0.1.13)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(zod@3.25.68)':
     dependencies:
       '@inkeep/cxkit-color-mode': 0.5.80
       '@inkeep/cxkit-theme': 0.5.80
@@ -6266,7 +6272,7 @@ snapshots:
       lucide-react: 0.503.0(react@19.0.0)
       marked: 15.0.12
       merge-anything: 5.1.7
-      openai: 4.78.1(encoding@0.1.13)(zod@packages+zod)
+      openai: 4.78.1(encoding@0.1.13)(zod@3.25.68)
       prism-react-renderer: 2.4.1(react@19.0.0)
       react: 19.0.0
       react-dom: 19.0.0(react@19.0.0)
@@ -6287,9 +6293,9 @@ snapshots:
       - supports-color
       - zod
 
-  '@inkeep/cxkit-react@0.5.80(@types/react-dom@19.0.4(@types/react@19.0.11))(@types/react@19.0.11)(encoding@0.1.13)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(zod@packages+zod)':
+  '@inkeep/cxkit-react@0.5.80(@types/react-dom@19.0.4(@types/react@19.0.11))(@types/react@19.0.11)(encoding@0.1.13)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(zod@3.25.68)':
     dependencies:
-      '@inkeep/cxkit-styled': 0.5.80(@types/react-dom@19.0.4(@types/react@19.0.11))(@types/react@19.0.11)(encoding@0.1.13)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(zod@packages+zod)
+      '@inkeep/cxkit-styled': 0.5.80(@types/react-dom@19.0.4(@types/react@19.0.11))(@types/react@19.0.11)(encoding@0.1.13)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(zod@3.25.68)
       '@radix-ui/react-use-controllable-state': 1.1.0(@types/react@19.0.11)(react@19.0.0)
       lucide-react: 0.503.0(react@19.0.0)
     transitivePeerDependencies:
@@ -6301,9 +6307,9 @@ snapshots:
       - supports-color
       - zod
 
-  '@inkeep/cxkit-styled@0.5.80(@types/react-dom@19.0.4(@types/react@19.0.11))(@types/react@19.0.11)(encoding@0.1.13)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(zod@packages+zod)':
+  '@inkeep/cxkit-styled@0.5.80(@types/react-dom@19.0.4(@types/react@19.0.11))(@types/react@19.0.11)(encoding@0.1.13)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(zod@3.25.68)':
     dependencies:
-      '@inkeep/cxkit-primitives': 0.5.80(@types/react-dom@19.0.4(@types/react@19.0.11))(@types/react@19.0.11)(encoding@0.1.13)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(zod@packages+zod)
+      '@inkeep/cxkit-primitives': 0.5.80(@types/react-dom@19.0.4(@types/react@19.0.11))(@types/react@19.0.11)(encoding@0.1.13)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(zod@3.25.68)
       class-variance-authority: 0.7.1
       clsx: 2.1.1
       merge-anything: 5.1.7
@@ -7821,6 +7827,8 @@ snapshots:
 
   '@types/resolve@1.20.2': {}
 
+  '@types/semver@7.7.0': {}
+
   '@types/unist@2.0.11': {}
 
   '@types/unist@3.0.3': {}
@@ -10153,7 +10161,7 @@ snapshots:
       regex: 6.0.1
       regex-recursion: 6.0.2
 
-  openai@4.78.1(encoding@0.1.13)(zod@packages+zod):
+  openai@4.78.1(encoding@0.1.13)(zod@3.25.68):
     dependencies:
       '@types/node': 18.19.103
       '@types/node-fetch': 2.6.12
@@ -10163,7 +10171,7 @@ snapshots:
       formdata-node: 4.4.1
       node-fetch: 2.7.0(encoding@0.1.13)
     optionalDependencies:
-      zod: link:packages/zod
+      zod: 3.25.68
     transitivePeerDependencies:
       - encoding
 
@@ -11542,7 +11550,7 @@ snapshots:
     dependencies:
       '@zod/core': 0.8.1
 
-  zshy@0.0.12(typescript@5.5.4):
+  zshy@0.0.13(typescript@5.5.4):
     dependencies:
       arg: 5.0.2
       globby: 14.1.0
diff --git a/scripts/semver-check.ts b/scripts/check-semver.ts
similarity index 97%
rename from scripts/semver-check.ts
rename to scripts/check-semver.ts
index bb3398b8..fb9d9b35 100644
--- a/scripts/semver-check.ts
+++ b/scripts/check-semver.ts
@@ -25,7 +25,7 @@ try {
   }
 
   console.log(`Valid semver version: ${version}`);
-} catch (error) {
+} catch (error: any) {
   console.error(`Error: ${error.message}`);
   process.exit(1);
 }
diff --git a/scripts/check-versions.ts b/scripts/check-versions.ts
new file mode 100644
index 00000000..9f03b270
--- /dev/null
+++ b/scripts/check-versions.ts
@@ -0,0 +1,58 @@
+import { readFileSync } from "node:fs";
+import { join } from "node:path";
+import { fileURLToPath } from "node:url";
+import { version } from "../packages/zod/src/v4/core/versions.js";
+
+const __dirname = fileURLToPath(new URL(".", import.meta.url));
+
+// Get zod package path
+const zodPackagePath = join(__dirname, "..", "packages", "zod");
+
+// Read package.json version
+const packageJsonPath = join(zodPackagePath, "package.json");
+const packageJson = JSON.parse(readFileSync(packageJsonPath, "utf8"));
+const packageJsonVersion = packageJson.version as string;
+if (typeof packageJsonVersion !== "string") {
+  throw new Error("package.json version is not a string");
+}
+
+// Read jsr.json version
+const jsrJsonPath = join(zodPackagePath, "jsr.json");
+const jsrJson = JSON.parse(readFileSync(jsrJsonPath, "utf8"));
+const jsrJsonVersion = jsrJson.version as string;
+if (typeof jsrJsonVersion !== "string") {
+  throw new Error("jsr.json version is not a string");
+}
+
+// read tag
+const tag = process.env.npm_config_tag || "latest";
+if (tag === "latest") {
+  // e.g. "beta"
+  const xyz = /^\d+\.\d+\.\d+$/;
+  if (!xyz.test(packageJsonVersion)) {
+    throw new Error("package.json version is not in x.y.z format");
+  }
+  if (!xyz.test(jsrJsonVersion)) {
+    throw new Error("jsr.json version is not in x.y.z format");
+  }
+}
+
+// Get version from versions.ts
+const versionsVersion = `${version.major}.${version.minor}.${version.patch}`;
+
+// Compare versions
+const isValid =
+  tag === "latest" ? packageJsonVersion === versionsVersion : packageJsonVersion.startsWith(versionsVersion);
+if (!isValid) {
+  console.error(`❌ Version mismatch:`);
+  console.error(`   package.json: ${packageJsonVersion}`);
+  console.error(`   versions.ts:  ${versionsVersion}`);
+  console.error(`   tag:          ${tag}`);
+  process.exit(1);
+} else {
+  if (tag === "latest") {
+    console.log(`✅ Versions match: ${packageJsonVersion} === ${versionsVersion}`);
+  } else {
+    console.log(`✅ Versions match: ${packageJsonVersion} starts with ${versionsVersion}`);
+  }
+}
diff --git a/tsconfig.json b/tsconfig.json
index 2f6a35ac..96395c98 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -2,8 +2,8 @@
   "extends": "./.configs/tsconfig.base.json",
   "compilerOptions": {
     "customConditions": ["@zod/source"],
-    "module": "node16",
-    "moduleResolution": "node16"
+    "module": "nodenext",
+    "moduleResolution": "nodenext"
   },
   "include": ["**/*.ts", "**/*.mts", "**/*.cts"],
   "exclude": ["**/*.d.ts", "**/*.d.cts", "**/*.d.mts", "packages/docs"]


Test patch:
diff --git a/packages/resolution/attw.test.ts b/packages/resolution/attw.test.ts
index beaf4cc0..754e4323 100644
--- a/packages/resolution/attw.test.ts
+++ b/packages/resolution/attw.test.ts
@@ -47,6 +47,24 @@ describe("Are The Types Wrong (attw) tests", () => {
 
       ***********************************
 
+      "zod/mini"
+
+      node10: 🟢 
+      node16 (from CJS): 🟢 (CJS)
+      node16 (from ESM): 🎭 Masquerading as CJS
+      bundler: 🟢 
+
+      ***********************************
+
+      "zod/locales"
+
+      node10: 🟢 
+      node16 (from CJS): 🟢 (CJS)
+      node16 (from ESM): 🎭 Masquerading as CJS
+      bundler: 🟢 
+
+      ***********************************
+
       "zod/v3"
 
       node10: 🟢 
diff --git a/packages/zod/src/v4/mini/tests/assignability.test.ts b/packages/zod/src/v4/mini/tests/assignability.test.ts
index c30d5ea9..0d173a6f 100644
--- a/packages/zod/src/v4/mini/tests/assignability.test.ts
+++ b/packages/zod/src/v4/mini/tests/assignability.test.ts
@@ -1,6 +1,6 @@
 import { test } from "vitest";
 
-import * as z from "zod/v4-mini";
+import * as z from "zod/mini";
 
 test("assignability", () => {
   // $ZodString
diff --git a/packages/zod/src/v4/mini/tests/computed.test.ts b/packages/zod/src/v4/mini/tests/computed.test.ts
index fed0c3a6..ce1f3746 100644
--- a/packages/zod/src/v4/mini/tests/computed.test.ts
+++ b/packages/zod/src/v4/mini/tests/computed.test.ts
@@ -1,5 +1,5 @@
 import { expect, test } from "vitest";
-import * as z from "zod/v4-mini";
+import * as z from "zod/mini";
 import { util as zc } from "zod/v4/core";
 
 test("min/max", () => {
diff --git a/packages/zod/src/v4/mini/tests/error.test.ts b/packages/zod/src/v4/mini/tests/error.test.ts
index 1ed5aec6..0e08be40 100644
--- a/packages/zod/src/v4/mini/tests/error.test.ts
+++ b/packages/zod/src/v4/mini/tests/error.test.ts
@@ -1,5 +1,5 @@
 import { expect, test } from "vitest";
-import * as z from "zod/v4-mini";
+import * as z from "zod/mini";
 
 test("no locale by default", () => {
   const result = z.safeParse(z.string(), 12);
diff --git a/packages/zod/src/v4/mini/tests/index.test.ts b/packages/zod/src/v4/mini/tests/index.test.ts
index 66fe5a1b..f489ff9b 100644
--- a/packages/zod/src/v4/mini/tests/index.test.ts
+++ b/packages/zod/src/v4/mini/tests/index.test.ts
@@ -1,5 +1,5 @@
 import { expect, expectTypeOf, test } from "vitest";
-import * as z from "zod/v4-mini";
+import * as z from "zod/mini";
 import type { util } from "zod/v4/core";
 
 test("z.boolean", () => {
diff --git a/packages/zod/src/v4/mini/tests/number.test.ts b/packages/zod/src/v4/mini/tests/number.test.ts
index e9c7776f..b9c3d800 100644
--- a/packages/zod/src/v4/mini/tests/number.test.ts
+++ b/packages/zod/src/v4/mini/tests/number.test.ts
@@ -1,5 +1,5 @@
 import { expect, expectTypeOf, test } from "vitest";
-import * as z from "zod/v4-mini";
+import * as z from "zod/mini";
 
 test("z.number", () => {
   const a = z.number();
diff --git a/packages/zod/src/v4/mini/tests/object.test.ts b/packages/zod/src/v4/mini/tests/object.test.ts
index 0d813d70..af43318b 100644
--- a/packages/zod/src/v4/mini/tests/object.test.ts
+++ b/packages/zod/src/v4/mini/tests/object.test.ts
@@ -1,5 +1,5 @@
 import { expect, expectTypeOf, test } from "vitest";
-import * as z from "zod/v4-mini";
+import * as z from "zod/mini";
 
 test("z.object", () => {
   const a = z.object({
diff --git a/packages/zod/src/v4/mini/tests/prototypes.test.ts b/packages/zod/src/v4/mini/tests/prototypes.test.ts
index b5609f1f..832e9db2 100644
--- a/packages/zod/src/v4/mini/tests/prototypes.test.ts
+++ b/packages/zod/src/v4/mini/tests/prototypes.test.ts
@@ -1,5 +1,5 @@
 import { expect, test } from "vitest";
-import * as z from "zod/v4-mini";
+import * as z from "zod/mini";
 
 declare module "zod/v4/core" {
   interface $ZodType {
diff --git a/packages/zod/src/v4/mini/tests/recursive-types.test.ts b/packages/zod/src/v4/mini/tests/recursive-types.test.ts
index 512c60d3..489308ac 100644
--- a/packages/zod/src/v4/mini/tests/recursive-types.test.ts
+++ b/packages/zod/src/v4/mini/tests/recursive-types.test.ts
@@ -1,5 +1,5 @@
 import { expect, expectTypeOf, test } from "vitest";
-import { z } from "zod/v4-mini";
+import { z } from "zod/mini";
 
 test("recursion with z.lazy", () => {
   const data = {
diff --git a/packages/zod/src/v4/mini/tests/string.test.ts b/packages/zod/src/v4/mini/tests/string.test.ts
index 3aabc383..ee5ed547 100644
--- a/packages/zod/src/v4/mini/tests/string.test.ts
+++ b/packages/zod/src/v4/mini/tests/string.test.ts
@@ -1,5 +1,5 @@
 import { expect, expectTypeOf, test } from "vitest";
-import * as z from "zod/v4-mini";
+import * as z from "zod/mini";
 
 const FAIL = { success: false };