Environment variables
Configuration that changes between machines, like database paths, API keys and the site's public URL, belongs in environment variables. Grimoire gives you four modules for reading them, split two ways: public or private, and read at build time or at run time.
Setting variables
Bun reads a .env file in the project root on its own, so for development:
DATABASE_PATH=books.sqlite
SESSION_SECRET=change-me
PUBLIC_SITE_NAME=Reading listKeep .env out of version control. In production, set real environment variables however your host does it (a systemd unit, docker run -e, a compose file).
Public and private
A variable whose name starts with PUBLIC_ is public: it may be sent to the browser. Everything else is private and stays on the server.
The private modules can only be imported by server code (+page.server.ts, +layout.server.ts, +server.ts, hooks.server.ts, and modules only they import). Importing one from a component fails the build, rather than shipping a secret in a bundle.
The four modules
| Module | Holds | Read |
|---|---|---|
$env/static/public | PUBLIC_* | when the app is built |
$env/static/private | everything else | once, when the server first loads it |
$env/dynamic/public | PUBLIC_* | on each access |
$env/dynamic/private | every variable | on each access |
The static modules export each variable by name, and also as an env object:
import { DATABASE_PATH } from "$env/static/private";
import { Database } from "bun:sqlite";
export const db = new Database(DATABASE_PATH, { create: true });The dynamic modules export only env, an object you read like process.env:
import { env } from "$env/dynamic/public";
export const Footer = () => <footer>{env.PUBLIC_SITE_NAME}</footer>;A variable that isn't set reads as an empty string from env. A named import is stricter: importing a variable that isn't set is an error, which fails the build for browser code and the module's first load on the server. The generated types list the variables in .env, so your editor flags a misspelled name.
Static or dynamic
In the browser, $env/static/public values are written into the JavaScript bundles by sigil build. They stay what they were at build time, which suits values that belong to the code.
$env/dynamic/public in the browser holds the values the server rendered into the page when it was requested, so changing a variable and restarting the server is enough; nothing needs rebuilding. That's the one to use when the same build runs in several places, such as a Docker image deployed to staging and production.
On the server, process.env works as well. The $env modules add the public/private split, the build-time check, and types.