Vite
Learn how to use ArkEnv in a Vite project.
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 initManual 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-pluginStandard 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-pluginnpm install @arkenv/standard valibot @valibot/to-json-schema
npm install -D @arkenv/vite-pluginConfiguration
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:
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.
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.
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.tsexecutes real@arkenv/coreor@arkenv/standardvalidation at boot. It ensures private secrets (such asDATABASE_URL) and public configuration validate before handling requests. - Client graph: During client compilation,
@arkenv/vite-pluginintercepts and transformsenv.ts. It inlines publicVITE_*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.