Skip to content

Environments and secrets

Nola has no environment system of its own. nola.config.ts is plain TypeScript, so environments are plain branching — plus one runtime-enforced switch and a clear rule about where keys come from.

  • Every provider factory reads its API key lazily, at the first request: openai() from OPENAI_API_KEY, anthropic() from ANTHROPIC_API_KEY, google() from GEMINI_API_KEY. Override the variable name with apiKeyEnv: "MY_VAR" or pass apiKey inline (prefer the variable).
  • In development, nola run and node --import nola-lang/register apply a project-root .env before evaluating the config. It follows the dotenv convention: a variable already set in the real environment wins over the file.
  • In production nothing loads .env. nola build output reads the real environment of the process — set the variables in your deployment platform.
nola.config.ts
import { mockProvider, openai } from "@nola-lang/providers";
import { defineConfig } from "@nola-lang/runtime";
const production = process.env.NODE_ENV === "production";
export default defineConfig({
providers: {
default: openai({ model: production ? "gpt-5" : "gpt-5-mini" }),
mock: mockProvider(() => ({ ok: true })),
},
// hermetic CI: EVERY ask goes to `mock`, even ones pinned with ask with / .withProvider()
forceProvider: process.env.CI ? "mock" : undefined,
});

forceProvider is the one runtime-enforced mechanism. Because it overrides every pin, a provider chosen inside a dependency cannot leak a real API call into a test run — see Providers and Testing.

Secrets are redacted from everything Nola logs or stores in a receipt — the built-in logger, hook payloads and AskReceipt never carry a raw key. The same helpers are exported for your own logging:

log.ts
import { redactError, redactSecrets } from "@nola-lang/runtime";
export const safe = (text: string) => redactSecrets(text);
export const safeError = (error: unknown) => redactError(error);
  • Keep .env in .gitignore — the starter’s ignore file already lists it beside node_modules/ and dist/; in a retrofitted project, add it yourself.
  • A replay ledger (nola.replay.jsonl) contains full prompts and answers. It is meant to be committed for deterministic tests — review what your contextual parameters put into it first.
  • Keys never belong in nola.config.ts; the file is bundled into dist/nola.config.js by nola build.

Next: Observability