Bun fullstack dev server

Learn how to use ArkEnv in a Bun fullstack application.

Edit on GitHub

A plain Bun script can call arkenv() with no extra plugin. Apps that bundle a client with Bun's fullstack dev server (Bun.serve()) or Bun's bundler (Bun.build()) need @arkenv/bun-plugin so the client bundle cannot see server secrets.

Backend-only script?

Stop at Getting started. You do not need this plugin when nothing ships to a browser.

Quickstart

arkenv init is the default when the app uses Bun.serve() or Bun.build(). It installs @arkenv/bun-plugin and writes env.ts.

npx arkenv init

Manual installation

Already ran init?

Skip this section. The CLI already installed the plugin and wrote the schema.

Install @arkenv/bun-plugin next to the engine you write schemas with, when bunfig.toml or the Bun.build script is already yours.

ArkType engine

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

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

Standard Schema engine

If you aren't using ArkType, install @arkenv/standard with Zod or Valibot, then import the plugin from @arkenv/bun-plugin/standard:

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

Full-stack setup

A full-stack Bun app has two graphs: the server process, and the client bundle. The plugin has to be on both the dev server and Bun.build, or the client graph still imports the real schema.

Define your schema

Create your schema in src/env.ts:

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

export const  = ({
  DATABASE_URL: "string",
  BUN_PUBLIC_API_URL: "string",
  BUN_PUBLIC_DEBUG: "boolean = false",
});

Configure dev server

Enable the plugin for development in bunfig.toml:

./bunfig.toml
[serve.static]
plugins = ["@arkenv/bun-plugin"]

Configure production build

Pass the plugin to Bun.build in your build script:

./build.ts
import  from "@arkenv/bun-plugin";

await Bun.build({
  : ["./src/index.tsx"],
  outdir: "./dist",
  : [()],
});

Import { env } from "./env" in application code. Transform mode inlines public BUN_PUBLIC_ keys into the client bundle and protects server secrets.

Backend-only Bun

No client bundle?

Skip @arkenv/bun-plugin. Call the runtime directly. The plugin is for Bun.serve() and Bun.build() client graphs.

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

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

Client access

Import the validated env object from your schema module.

Do not read process.env directly

Reading process.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/bun-plugin.

SSR and bundling execution

Full-stack Bun applications separate server runtime execution from client bundle generation:

  • Server execution: Server entrypoints and backend APIs execute real @arkenv/core or @arkenv/standard validation at startup, validating database URLs and backend secrets before serving traffic.
  • Client bundling: During Bun.build runs or [serve.static] requests, @arkenv/bun-plugin transforms env.ts imports in client code. It inlines BUN_PUBLIC_* values as static literals and replaces server keys with throwing runtime stubs. Browser bundles omit the validator engine.

Next steps