Migrating to v1
Move a v0 project to the v1 packages and the canonical env object.
v1 splits the old arkenv library from the CLI, and it uses the same
import { env } from "./env" surface on every host. Vite and Bun no
longer take a schema in the plugin call. Packages ship as
1.0.0-rc.n under the rc npm tag (and latest during the RC window);
APIs are frozen for the final validation pass before 1.0.0.
If you are coming from T3 Env rather than ArkEnv v0, use Migrating from T3 Env.
Set TypeScript moduleResolution to "bundler", "node16", or
"nodenext". Legacy "node" does not load package exports subpaths
such as @arkenv/standard/valibot.
Package names
On v0, arkenv on npm was the runtime. On v1 that name is the CLI.
Importing arkenv as a library throws. Swap packages first.
| v0 | v1 | Role |
|---|---|---|
arkenv | @arkenv/core | Runtime and ArkType engine |
@arkenv/cli | arkenv | CLI (init, check) |
| — | @arkenv/standard | Zod, Valibot, and other Standard Schema validators |
@arkenv/vite-plugin | @arkenv/vite-plugin | Vite plugin (transform mode only) |
@arkenv/bun-plugin | @arkenv/bun-plugin | Bun plugin (transform mode only) |
@arkenv/nextjs | @arkenv/nextjs | Next.js adapter |
@arkenv/nuxt | @arkenv/nuxt | Nuxt module |
Swap the runtime and CLI packages
Remove the v0 library (and @arkenv/cli if you had it), then install
@arkenv/core plus the CLI:
npm rm arkenv @arkenv/cli
npm install @arkenv/core arktype
npm install -D arkenvpnpm remove arkenv @arkenv/cli
pnpm add @arkenv/core arktype
pnpm add -D arkenvyarn remove arkenv @arkenv/cli
yarn add @arkenv/core arktype
yarn add --dev arkenvbun remove arkenv @arkenv/cli
bun install @arkenv/core arktype
bun install --dev arkenvIf you want Zod or Valibot without ArkType, install
@arkenv/standard instead of
@arkenv/core and arktype.
Point imports at the new packages
Replace from "arkenv" with from "@arkenv/core" (or
@arkenv/standard). Framework packages keep their names. Keep
exporting env from your schema module and import it as
{ env } from "./env".
Canonical env object
Every framework now uses one validated object:
import { env } from "./env";On v0, Vite and Bun plugins accepted the schema as the first argument
(arkenv({ VITE_API_URL: "string" })), rewrote import.meta.env or
process.env, and asked you to merge ImportMetaEnvAugmented or
ProcessEnvAugmented into a .d.ts file. That schema/define API is
gone. Those plugins only rewrite imports of ./env in the client graph.
They do not take a schema.
The ambient path could only check values on the build machine, and it
only rewrote static reads. Spreads, import.meta.env[key], and aliases
still saw raw strings while TypeScript claimed parsed types. A real
env.ts module lets the plugin inline public keys and stub server
secrets, and it validates private keys when the server boots.
Vite
Follow these steps if the v0 plugin still receives a schema argument.
Export env from env.ts
import from "@arkenv/core";
export const = ({
: "string",
: "string",
: "number.port = 3000",
});Register the plugin with no schema
import { defineConfig } from "vite";
import arkenvPlugin from "@arkenv/vite-plugin";
export default defineConfig({
- plugins: [arkenvPlugin({
- VITE_API_URL: "string",
- DATABASE_URL: "string",
- })],
+ plugins: [arkenvPlugin()],
});For Zod or Valibot, import from @arkenv/vite-plugin/standard.
Delete ambient ImportMetaEnv merges
/// <reference types="vite/client" />
-
- type ImportMetaEnvAugmented = import("@arkenv/vite-plugin").ImportMetaEnvAugmented<
- typeof import("./env").Env
- >;
-
- interface ImportMetaEnv extends ImportMetaEnvAugmented {}Read env in application code
+ import { env } from "../env";
function App() {
- const apiUrl = import.meta.env.VITE_API_URL;
+ const apiUrl = env.VITE_API_URL;
return <div>API: {apiUrl}</div>;
}See the Vite guide for transform mode and
reusing a compiled schema in vite.config.ts.
Bun
Same shape as Vite: schema in env.ts, plugin with no schema argument.
Export env from env.ts
import from "@arkenv/core";
export const = ({
: "string.url",
: "string",
});Register @arkenv/bun-plugin without a schema
Keep the plugin in bunfig.toml or Bun.build. Do not pass the
schema object. Backend-only processes can skip the plugin and call
@arkenv/core directly. See the Bun guide.
Delete ambient ProcessEnv merges
/// <reference types="bun-types" />
-
- type ProcessEnvAugmented = import("@arkenv/bun-plugin").ProcessEnvAugmented<
- typeof import("./src/env").default
- >;
-
- declare namespace NodeJS {
- interface ProcessEnv extends ProcessEnvAugmented {}
- }Read env in application code
+ import { env } from "../env";
export function App() {
- const apiUrl = process.env.BUN_PUBLIC_API_URL;
+ const apiUrl = env.BUN_PUBLIC_API_URL;
return <div>API: {apiUrl}</div>;
}Next.js and Nuxt
Package names for @arkenv/nextjs and @arkenv/nuxt did not change.
After you switch the runtime import from arkenv to @arkenv/core (or
the framework package), keep using withArkEnv or the Nuxt module.
Nested arkenv({ server, client, shared }) still runs. Prefer a flat
schema. See
Client vs. server
and the Next.js /
Nuxt guides.
Strict layout removed (alpha hard cut)
Earlier alphas shipped npx arkenv init --strict plus
@arkenv/nextjs/server / @arkenv/nuxt/client (and related subpaths).
That layout engine is gone. Flat env.ts is the only first-class path.
If you relied on --strict for name/type isolation, keep two modules by
hand — the same shape as two T3 createEnv calls:
import arkenv from "@/.arkenv";
export const env = arkenv({
NEXT_PUBLIC_API_URL: "string",
});import "server-only";
import arkenv from "@arkenv/core";
import { env as clientEnv } from "./client";
export const env = arkenv(
{ DATABASE_URL: "string" },
{ extends: [clientEnv] },
);On Nuxt, use @arkenv/nuxt for the client module and @arkenv/core for
the server module. Never import the server module from client code.
Full recipe:
Client vs. server.
Dead-code elimination
if (import.meta.env.VITE_FLAG) can constant-fold in a bundler.
if (env.VITE_FLAG) does not. v1 takes that trade-off: the client stub
is a few bytes per key, no validator ships to the browser, and server
secrets stay off the client.
Deprecated APIs
These still work on @arkenv/nextjs and
@arkenv/nuxt. They will be removed in a later
release.
- Nested
arkenv({ server, client, shared }). Usearkenv(schema, options)instead. - Options
exposeandshared. UseexposeToClient. See Client vs. server. - Config
layout: "simple". Use"flat".
The changelog lists what changed in each release.