Using CtroEnv in Deno and Bun
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:
import.meta.env— Vite, Astro, modern frameworksDeno.env— Deno runtimeBun.env— Bun runtimeprocess.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.envnor runtime globals
Fix: pass source explicitly or use objectSource() in tests.
Environment-Specific Differences
| Feature | Node.js | Deno | Bun |
|---|---|---|---|
| Env access | process.env | Deno.env.get() | Bun.env |
.env file loading | @ctroenv/node | Manual | Manual |
| Source adapter needed | Optional | Auto-detected | Auto-detected |
| Detection check | typeof process | typeof globalThis.Deno | typeof 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.