Using CtroEnv in Deno and Bun

2026-06-23·Ctrotech·
guidedenobun

Using CtroEnv in Deno and Bun

CtroEnv's runtime detection works across Node.js, Deno, and Bun without configuration. Write one schema, use it anywhere.

Auto-Detection

When you call defineEnv() without an explicit source, detectSource() checks for runtimes in this order:

  1. import.meta.env — Vite, Astro, modern frameworks
  2. Deno.env — Deno runtime
  3. Bun.env — Bun runtime
  4. process.env — Node.js
import { defineEnv, string, number } from "@ctroenv/core"

// Works in Node, Deno, AND Bun — no adapter needed
const env = defineEnv({
  DATABASE_URL: string().url(),
  PORT: number().port().default(3000),
})

How Detection Works

The detection functions probe for global objects specific to each runtime:

// Deno — checks for globalThis.Deno.env.get
function tryDenoEnv() {
  const deno = globalThis.Deno
  if (deno?.env?.get) return { get: (k) => deno.env.get(k) }
}

// Bun — checks for globalThis.Bun.env
function tryBunEnv() {
  const bun = globalThis.Bun
  if (bun?.env) return { get: (k) => bun.env[k] }
}

These checks are lightweight — they test object existence without importing anything.

Deno

Deno passes env vars through Deno.env.get():

// example.ts
import { defineEnv, string, number } from "npm:@ctroenv/core"

const env = defineEnv({
  DATABASE_URL: string().url(),
  PORT: number().port().default(3000),
})

console.log(env.PORT)
DATABASE_URL="postgres://localhost:5432/db" deno run -A example.ts
# 3000

The -A flag grants the necessary permissions for env access. Use --allow-env for a more restrictive setup.

Bun

Bun surfaces env vars through Bun.env — an object similar to process.env:

// example.ts
import { defineEnv, string, number } from "@ctroenv/core"

const env = defineEnv({
  DATABASE_URL: string().url(),
  PORT: number().port().default(3000),
})
DATABASE_URL="postgres://localhost:5432/db" bun run example.ts

Explicit Source

You can always pass the source explicitly for clarity:

import { defineEnv, string } from "@ctroenv/core"

// Deno
const env = defineEnv(schema, {
  source: { get: (key) => Deno.env.get(key) },
})

// Bun
const env = defineEnv(schema, {
  source: { get: (key) => Bun.env[key] },
})

When Auto-Detection Fails

If none of the four detection methods find a runtime, detectSource() throws:

Error: No environment source detected. Pass `source` explicitly to `defineEnv()`.

This happens in:

  • Pure browser environments (no process.env)
  • Custom runtimes without global env objects
  • Test runners that mock neither process.env nor runtime globals

Fix: pass source explicitly or use objectSource() in tests.

Environment-Specific Differences

FeatureNode.jsDenoBun
Env accessprocess.envDeno.env.get()Bun.env
.env file loading@ctroenv/nodeManualManual
Source adapter neededOptionalAuto-detectedAuto-detected
Detection checktypeof processtypeof globalThis.Denotypeof globalThis.Bun

The core package handles all three. Framework adapters (@ctroenv/node, @ctroenv/vite, @ctroenv/nextjs) are Node/Vite-specific additions for .env file loading and build-time validation.