Vite

Learn how to use ArkEnv in a Vite project.

Edit on GitHub

Vite reports that it is ready before your schema has run, unless something validates during config resolution. ArkEnv does that check, and keeps server secrets out of the client bundle.

When does validation run?

When @arkenv/vite-plugin is registered, it discovers and validates env.ts during Vite config resolution. An invalid or missing value aborts the dev server or production build before Vite reports that it is ready. Relevant .env and schema changes are validated again during HMR.

Without the plugin, validation is import-driven: env.ts runs when an application module imports it. That applies to both the server graph and the client graph. The plugin is what lets Vite validate the schema before the graph is ready, and rewrite client imports so public VITE_ keys inline. See the @arkenv/vite-plugin reference.

There is no default-off lazy-validation flag.

How this host compares with Next.js, Nuxt, and Bun is in Frameworks.

Quickstart

arkenv init is the default on an existing Vite app. It installs @arkenv/vite-plugin, configures vite.config.ts, and scaffolds env.ts.

npx arkenv init

Manual installation

Already ran init?

Skip this section. The CLI already installed the plugin and wrote vite.config.ts.

Install @arkenv/vite-plugin next to the engine you write schemas with, when vite.config.ts is already in the repo and you want each package explicit in package.json.

ArkType engine

Install @arkenv/core, arktype, and the Vite plugin:

npm install @arkenv/core arktype
npm install -D @arkenv/vite-plugin

Standard Schema engine

If you aren't using ArkType, install @arkenv/standard with Zod or Valibot:

npm install @arkenv/standard zod
npm install -D @arkenv/vite-plugin
npm install @arkenv/standard valibot @valibot/to-json-schema
npm install -D @arkenv/vite-plugin

Configuration

Vite does not know about your schema until the plugin is in the plugins array. arkenvPlugin() is what validates during config resolution and rewrites client imports.

Register the plugin

Import the plugin and add it next to the rest of your Vite plugins:

./vite.config.ts
import { defineConfig } from "vite";
import  from "@arkenv/vite-plugin";

export default defineConfig({
  : [()],
});

Zod and Valibot import the plugin from @arkenv/vite-plugin/standard.

Define your schema

The plugin looks for env.ts. Import the runtime from @arkenv/core, @arkenv/standard, or @arkenv/standard/valibot. The arkenv package is the CLI only. Importing it as a library throws.

./env.ts
import  from "@arkenv/core";

export const  = ({
  DATABASE_URL: "string",
  VITE_API_URL: "string",
  : "'development' | 'production' | 'test' = 'development'",
});

Import { env } from "./env" across your application. In server contexts (such as SSR or development scripts), the real validation module runs at boot. In client bundles, the Vite plugin rewrites the import to inline public VITE_ values and guards private server keys.

Reuse the schema in Vite config

Vite does not load .env* files while evaluating vite.config.ts. When the config needs typed values (for example server.port), compile a reusable schema with type() and validate it with core arkenv() and loadEnv. Register the plugin with no schema argument — it still rewrites env.ts in the client graph.

./vite.config.ts
import { defineConfig,  } from "vite";
import  from "@arkenv/vite-plugin";
import , {  } from "@arkenv/core";

export const  = ({
  PORT: "number.port = 5173",
  VITE_API_URL: "string",
  DATABASE_URL: "string",
});

export default defineConfig(({ mode }) => {
  const  = (, { : (mode, process.cwd(), "") });
  return {
    // Plugin is transform-only — no schema argument
    : [()],
    : { : .PORT },
  };
});

Learn more in Reusing schemas.

Client access

Import the validated env object from your schema module.

Do not read import.meta.env directly

Reading import.meta.env skips ArkEnv. Import { env } so public keys stay typed and coerced and server secrets stay out of the client bundle.

Types and options: @arkenv/vite-plugin.

SSR and dual-graph execution

Vite maintains two distinct module graphs when building and serving full-stack or SSR applications:

  • Server graph: During SSR execution and dev server requests, env.ts executes real @arkenv/core or @arkenv/standard validation at boot. It ensures private secrets (such as DATABASE_URL) and public configuration validate before handling requests.
  • Client graph: During client compilation, @arkenv/vite-plugin intercepts and transforms env.ts. It inlines public VITE_* values as static literals and replaces private server keys with throwing runtime stubs. Client bundles omit the validator engine.

On Vite 6+, the Environment API drives this split: server consumers and custom ssr environments keep the real module, everything else gets the client rewrite. On Vite 4 and 5, the plugin falls back to the legacy SSR transform flag.

Next steps