nola.config.ts
nola.config.ts lives at the project root and default-exports a defineConfig call. providers.default is required; everything else is optional.
import { openai } from "@nola-lang/providers";import { defineConfig } from "@nola-lang/runtime";
export default defineConfig({ providers: { // Reads OPENAI_API_KEY from the environment at the first ask. default: openai({ model: "gpt-5-mini" }), },});The import surface is frozen
Section titled “The import surface is frozen”Use exactly these two specifiers:
import { defineConfig } from "@nola-lang/runtime";import { openai, mockProvider, withRetry } from "@nola-lang/providers";defineConfig and everything app-facing come from @nola-lang/runtime. Everything provider-shaped — provider factories (openai, anthropic, google, mockProvider), resilience combinators (withRetry, fallback, roundRobin, constant, exponential) and record/replay (record, replay) — comes from @nola-lang/providers.
Never import providers from the runtime, never import defineConfig from the providers package, and never reach for a subpath (@nola-lang/runtime/config, @nola-lang/runtime/providers, nola-lang/runtime) — those do not exist. @nola-lang/providers deliberately does not depend on the runtime, which is what keeps a second copy of the runtime out of the install tree.
Validated and frozen
Section titled “Validated and frozen”The config is loaded once, validated, and frozen. An unknown top-level key, a provider (singular) key, or a providers map without default is rejected with NOLA3003, naming the file and the field; the reserved plugins key is NOLA3005. There is no runtime mutation API: the resolved config latches on the first ask, and reconfiguring after that is an error (nolaRuntime.reset() exists for tests). Every key, its type, default and whether the runtime reads it: Config schema.
How it reaches run time
Section titled “How it reaches run time”nola run/node --import nola-lang/register— the loader bundles and evaluatesnola.config.tsfor the project, applies a project-root.envfirst, and configures the process before your entry runs.nola build— for app projects (the default) it bundles the config intodist/nola.config.jswith a self-configuring wrapper and appends an import of it to every built.tsi.jsmodule, so the built modules run under plainnodewith no loader and no manual configuration. (nola buildcompiles.tsionly — see Deploying for the entry-point recipe.)build.target: "lib"opts out of that wiring: pure lowered JS, and the consuming app’s process supplies the config.- Bundler plugins (
@nola-lang/vite,webpack,rollup,rolldown,esbuild,rspack, and@nola-lang/next) wire the config into the server bundle themselves — see The nola CLI and the plugin READMEs on npm.
- The config cannot import
.tsimodules — it is evaluated before the Nola loader registers (NOLA3012). - Keep
compiler.underivableContextTypea literal value: the editor reads it statically and cannot execute your config. - Secrets come from the environment (
.envin development, the real environment in production) — never inline keys. See Environments and secrets.
Section map
Section titled “Section map”| Key | What it does | Page |
|---|---|---|
providers |
the named provider map; default required |
Providers |
forceProvider |
hermetic override — every ask goes here | Providers |
observability, hooks |
logging level, event hooks, receipts | Observability |
ask, system |
per-invocation timeout, extra system text | Ask options |
compiler, build |
underivableContextType; build.target |
Config schema |
middleware, cache |
validate but are not wired at 0.1.x | Config schema |
plugins |
reserved — rejected at load | — |
Next: Providers