gaffer.toml
gaffer.toml lives at the root of a gaffer project and declares its projections, the environments they connect to, and per-projection settings. gaffer init writes the initial file.
Example
Section titled “Example”[env.local]connection = "kurrentdb://localhost:2113?tls=false"default = true
[[projection]]name = "order-count"entry = "projections/order-count.js"engine_version = 2fixtures.happy = "fixtures/orders.json"fixtures.full = "fixtures/orders-full.json"
[[projection]]name = "order-totals"entry = "projections/order-totals.js"engine_version = 2Top-level keys
Section titled “Top-level keys”[env.<name>]
Section titled “[env.<name>]”[env.local]connection = "kurrentdb://localhost:2113?tls=false"default = true
[env.staging]connection = "kurrentdb://admin:${DB_PASSWORD}@staging:2113"
[env.prod]connection = "${KURRENT_PROD_CONNECTION}"production = trueAn environment names a KurrentDB connection. Select one with gaffer dev --env <name>, or pick it from the interactive prompt. Environment names must match ^[A-Za-z0-9_-]+$.
connection: KurrentDB connection string for the environment. Required. Used when running a projection against a live event stream (gaffer dev <projection>without--eventsor--fixture). Override per-invocation with--connection, which bypasses the whole block (its OAuth, client certificate, andproductionsettings).default: optional bool. At most one environment may setdefault = true. It’s used when--envis omitted; without a default, an interactivegaffer devprompts you to pick an environment, while a non-interactive run requires--env(fixture runs need no environment). Two defaults is a config error.production: optional bool.production = truemarks the environment’s database as production, activating the production guard tier: deploy and operate confirmations name the target as production, and--no-validateis refused. A database can also declare itself production through its$server-infostream; the two signals combine as an OR, so the flag is opt-in only.production = false(the same as omitting it) defers to the server’s own declaration. It never downgrades a database that declares itself production. The flag travels with environment selection: a target reached via--connectionhas no environment, so only the server’s own declaration applies.user_cert_file/user_key_file: optional paths to an X.509 user certificate and its private key, for authenticating to KurrentDB with a client certificate. Both must be set together. The certificate is presented in the TLS handshake, so the connection must use TLS. A client certificate is independent of OAuth, so an environment may use both. Likeconnection, the paths expand${VAR}references and resolve relative to the project root when not absolute, so a relative path works regardless of the directorygafferruns from.
[env.staging]connection = "kurrentdb://staging:2113?tls=true"user_cert_file = "certs/user.crt"user_key_file = "${CERT_DIR}/user.key"${VAR} references in connection are expanded so credentials need not be committed:
[env.staging]connection = "kurrentdb://admin:${DB_PASSWORD}@staging:2113"Values resolve, highest precedence first, from the shell environment, then a per-environment .env.<env> file, then the base .env file at the project root. A referenced variable that isn’t set is an error; only the braced ${...} form is a reference, so a bare $ is left untouched.
[env.<name>.oauth]
Section titled “[env.<name>.oauth]”[env.staging.oauth]issuer = "https://idp.example.com/realms/kurrent"client_id = "kurrentdb-client"scopes = ["openid"]Authenticate to the environment with OAuth/OIDC bearer tokens instead of a username and password. Endpoints are discovered from the issuer’s /.well-known/openid-configuration.
issuer: OIDC issuer URL. Required. Must behttps(anhttploopback issuer is allowed for local development).client_id: OAuth client ID. Required.scopes: optional list of scopes to request.audience: optional audience parameter, for identity providers that require one (e.g. Auth0).ca_file: optional path (relative to the project root, or absolute) to a PEM CA bundle for verifying the issuer’s TLS, when the provider is served by an internal or self-signed CA. A CA certificate is public, so it lives here rather than in the environment.
The client secret is never written to gaffer.toml. Its presence in the environment selects how a token is obtained:
- With
KURRENTDB_OAUTH_CLIENT_SECRETset: gaffer uses the non-interactive client-credentials grant, for CI and automation. The secret resolves with the same precedence asconnectionvariables (shell, then.env.<env>, then.env). - Without it: run
gaffer auth --env <name>once to sign in through the browser. The token is stored in the OS keyring, bound to the host the environment’s connection names (environments naming the same host share it; a different host needs its own sign-in), and refreshed automatically. If it can no longer be refreshed, gaffer discards it and prompts you to sign in again rather than reporting a connection error.GAFFER_NO_OPENprints the authorization URL instead of launching a browser. With no OS keyring the token is kept in an encrypted file protected by a passphrase;GAFFER_KEYRING_PASSWORDsupplies that passphrase where no terminal is available to prompt on (CI, or an editor-spawned process), and without it gaffer fails with guidance rather than hanging.GAFFER_KEYRING_NAMEisolates the file store in a per-client directory (keyring-<name>), so a client that injects its own passphrase doesn’t lock the shared default store.
gaffer auth --clear removes every stored token, signing out of all environments. It needs neither the keyring passphrase nor a gaffer project, so it also resets a keyring whose passphrase has been forgotten.
quirks_version
Section titled “quirks_version”quirks_version = "26.1.0"Target KurrentDB version (MAJOR.MINOR.PATCH). Gaffer turns off engine quirks that have been fixed in the named version or later. Unset means gaffer reproduces every known KurrentDB quirk.
The GAFFER_QUIRKS_VERSION environment variable overrides every quirks_version in the file. Useful for CI matrices.
Optional.
[database_config]
Section titled “[database_config]”Node-level engine settings: the configuration gaffer expects on the deployment target. The key names and grouping mirror the databaseConfig group in the @kurrent/projections-testing library (in TOML’s snake_case form), though local enforcement differs as noted below. max_state_size applies to local runs (gaffer dev and the testing library); the timeouts do not.
[database_config]max_state_size = 16777216compilation_timeout = 500execution_timeout = 250max_state_size: maximum size in bytes of a projection’s serialized state, mapping to the server’sMaxProjectionStateSize. Default and ceiling are both 16777216 (16 MiB); the server rejects a larger value. Enforced on local runs: gaffer faults a projection whose state would exceed the cap, so you catch state bloat before deploy. The boundary is an approximation, not an exact match, because gaffer measures UTF-8 serialized bytes rather than the server’s own measure. A non-positive value is ignored and the default applies.compilation_timeout/execution_timeout: time limits in milliseconds the server applies to compiling a projection and to each handler invocation, mapping to itsProjectionCompilationTimeout(default 500ms) andProjectionExecutionTimeout(default 250ms). They are declaration only: gaffer records them for configuration checks against a deployment target, but does not apply them to local runs, because a wall-clock budget measured on your machine isn’t comparable to the server’s. To bound how long a local projection may run before gaffer treats it as hung, set theGAFFER_TIMEOUT_MSenvironment variable (milliseconds, applied to both phases). You would only raise it from gaffer’s built-in 5000ms default on slow hardware.
All keys are optional; omit the section to take the defaults.
gaffer deploy and gaffer status check the declared values against the target node’s live settings and warn on a divergence, so a server enforcing different limits than your fixtures assumed is visible before it bites. The check is advisory: when the node’s options can’t be read (no HTTP surface, an auth refusal), both commands warn that the check couldn’t run instead of failing or reporting a false in-sync. The read authenticates exactly like the connection itself - basic credentials (from .env/.env.<env> or the connection string), an OAuth bearer token, or the environment’s user certificate, honouring tlsCaFile and tlsVerifyCert. gaffer status --json carries any divergence in its configDrift array, or the failure reason in configDriftError.
telemetry
Section titled “telemetry”telemetry = falseProject-level telemetry opt-out. Setting false disables telemetry for any gaffer command run inside this project, regardless of the user’s own opt-out state.
For user-level opt-outs that apply across every project, see Telemetry.
Optional. Telemetry is on by default.
Per-projection keys
Section titled “Per-projection keys”Each projection is a [[projection]] table-array entry.
name = "order-count"Lookup key for gaffer dev <name>, gaffer info <name>, the VS Code lens, and MCP tools. Required. Names must be unique within a project.
entry = "projections/order-count.js"Path to the projection’s JavaScript source file, relative to the project root. Required. Absolute paths (and paths that escape the project root) are rejected.
fixtures.<name>
Section titled “fixtures.<name>”fixtures.happy = "fixtures/orders.json"fixtures.full = "fixtures/orders-full.json"Named JSON events files. Run with gaffer dev <name> --fixture happy. Path is relative to the project root; absolute paths (and paths that escape the project root) are rejected.
Optional. Add one entry per scenario you want to re-run.
engine_version
Section titled “engine_version”[[projection]]name = "order-count"entry = "projections/order-count.js"engine_version = 2Projection engine version: 1 or 2. Required on every projection. V1 is for legacy compatibility with older KurrentDB releases; V2 is the default for new projections written by gaffer scaffold. There is no top-level fallback, so each projection states its own version. It is fixed at create time on the server, so gaffer deploy refuses an in-place update that changes it rather than recreating the projection and dropping its state.
track_emitted_streams
Section titled “track_emitted_streams”[[projection]]name = "order-events"entry = "projections/order-events.js"engine_version = 1track_emitted_streams = trueRecords the streams a projection emits to, mirroring the KurrentDB V1 projection option of the same name. Bool, optional, and valid only when the projection’s engine_version = 1. Setting it on a V2 projection raises the quirk.trackEmittedStreams.unsupportedOnV2 diagnostic: gaffer info, dev, and diff still show the projection’s analysis, while gaffer deploy and gaffer recreate refuse it before any write. Like engine_version, it is fixed at create time, so gaffer deploy refuses an in-place update that changes it.
quirks_version (per-projection)
Section titled “quirks_version (per-projection)”[[projection]]name = "v26-only"entry = "projections/v26-only.js"quirks_version = "26.1.0"Per-projection override of the top-level quirks_version. Optional.
execution_timeout (per-projection)
Section titled “execution_timeout (per-projection)”[[projection]]name = "slow-projection"entry = "projections/slow-projection.js"execution_timeout = 30000Declares a per-projection override of [database_config].execution_timeout on the deployment target, for a projection with long-running handlers (large reductions, heavy regex work). Like the [database_config] timeouts, it is declaration only and is not applied to local runs. Optional.
Resolution order
Section titled “Resolution order”Settings that exist at both top-level and per-projection resolve from most-specific to least:
| Setting | Resolution |
|---|---|
engine_version | Per-projection only. Required on each [[projection]]. |
quirks_version | GAFFER_QUIRKS_VERSION env > per-projection > top-level > unset. |
max_state_size | [database_config] > 16777216 (16 MiB). Enforced locally. |
compilation_timeout / execution_timeout | Declaration only; not applied locally. The local hang-guard is GAFFER_TIMEOUT_MS > 5000ms. |
connection | --connection flag > selected env (--env, or the default env). |