Skip to main content
Configuration is optional. All services start with sensible defaults. To customize seed data, create baseline.config.json in your project root (or pass --seed). Run npx @deploybaseline/cli init to generate a starter file:
baseline.config.json
The $schema line points editors such as VS Code and JetBrains IDEs at the config schema, so they complete keys and flag mistakes while you type. init and convert write the URL for the CLI’s own version, so the editor checks what that CLI accepts; change the version in the URL when you upgrade. It is optional, and the CLI ignores it. The CLI also reads baseline.config.ts / .mts / .js / .mjs (see Custom emulators), baseline.config.yaml / .yml, and the older service-emulator.config.json / .yaml / .yml. Keep one config file per folder: with more than one, start asks you to choose with --config.

Checking a config

validate checks a config file without starting anything and names each problem with its line and column, the setting, and a fix:
It runs three checks, each once the one before it passes:
  1. Syntax. A .json file must be strict JSON. Trailing commas, comments, single quotes, and unquoted names are reported where they are, with the fix. Duplicate keys are reported too, since JSON would silently keep the last one.
  2. Shape. Every key and value is checked against baseline.config.schema.json, which holds each provider’s seed shape: unknown keys (with the closest known one), missing required fields, wrong types, and values outside a fixed set. A service under services with emulator: stripe has its seed checked as Stripe seed data.
  3. Meaning. The checks start runs when it loads the config: custom emulator modules, version ranges, ports, and shared identities.
validate <file> checks another file, and --json prints the result as JSON (valid, and problems with path, line, column, message, and hint) for scripts and agents. It exits 1 when there are problems, so CI can run it before the tests. start runs the same shape check and prints a warning for each problem, then starts anyway. Seeds for provider packages that are not built in (provider: npm:...) and custom emulators are checked by that code when it starts. To move a YAML config to JSON, run convert. It writes baseline.config.json beside the YAML file with $schema added, checks it, and leaves the YAML file for you to delete. JSON has no comments, so it tells you how many were left behind:

Shared identities

identities declares people once and seeds each of them as a user in every selected provider that has users (GitHub, Google, Slack, Apple, Microsoft, Okta, Clerk, Linear, Jira, Auth0, and Salesforce). One person then signs in to GitHub, Google, Slack, and Okta with the same email:
  • username: login where a provider has one; defaults to the part before ”@”
  • github: fields merged over the mapped user
  • slack: leave Ada out of Slack
Each identity is mapped to the provider’s own user fields (for example login on GitHub, first_name and last_name on Okta, email_addresses on Clerk). The first word of name becomes the given name and the rest the family name. Users listed in a provider’s own section come first, so the default token user does not change, and an identity whose email that section already lists is skipped. Identities apply to the CLI and config files; with createEmulator, pass users in the service’s seed.

Saved state

state keeps every service’s data, tokens, and webhook subscriptions across restarts. It is the config form of --state:
or, with a save delay:
A relative path resolves against the config file’s folder. url also takes sqlite:.baseline/state.db or a libsql:// URL; keep a libSQL token out of the config file with BASELINE_STATE_AUTH_TOKEN. Saved state wins over seed config; see Saved state for precedence, --reset-state, watch reloads, locking, and SQLite and Turso.

Ports

With no config and no --service, each service listens on the base port plus its fixed slot (GitHub 4001 through Twilio 4012, see Getting started). Slots never change between releases: new providers are added after Twilio, and they start only when you name them in config or --service. When config or --service names services, they take consecutive ports from the base port, in that same order. To keep a port stable no matter which services you run, set port per service:

Console port

The console listens one below the base port (3999 by default). It serves the API reference for the services you started at /docs, and the control API below. --console-port <port> moves it and --no-console turns it off. A service set to the console port is an error at start, and when another program holds the port, the services start without the console.

One process per service

Each built-in service runs in its own process and listens on its own port. Its output is prefixed with [<service>]. A service that crashes restarts on the same port from the last state Baseline saved for it, up to three times a minute; after that it stays down until you restart Baseline. Custom emulators from a config file run in the CLI’s own process.

Single port

--single-port is not supported. With every service in its own process, nothing sits in front of them to route /<service> paths, so the flag stops at start with an error. In a container, publish the port range instead and point each SDK at its service’s port:

Fault injection

Add latency and errors to any service to test retries and timeouts. Faults are off unless configured. Set them per service in config:
  • seed: same sequence of failures every run
  • rate: 20% of matching requests
match is "*", a path, or "METHOD /path"; paths may use * and :param segments. A rule needs latency_ms, response (status, optional body and headers), or both. Presets: faults: { profile: degraded } (300 ms latency and 10% 503s) and faults: { profile: throttled } (30% 429s with Retry-After). Injected responses carry X-Baseline-Fault: injected. createEmulator({ service, faults }) (or faults in its seed) takes the same config.

Control API

The console port and every service port answer /_baseline/control and /_baseline/faults for the whole run. Start prints the console’s address. Start prints a per-process token; send it as X-Baseline-Control-Token. Requests must use a loopback host or a configured base URL host, and cross-site requests are rejected. Start also prints an inspector link to /_baseline/control/inspector with the token in it, with a Faults tab and a State tab. Opening the link swaps the token for an HttpOnly, SameSite=Strict cookie that only allows reads, so API changes always need the header; the State tab’s buttons carry a form token that only the inspector page holds. /_baseline/state serves the state endpoints (see Saved state).
Each CLI process writes its token and ports to .baseline/run/<pid>.json (owner-only) next to your config, so scripts can find them. Add .baseline/ to .gitignore.

Provider seed config

Each provider’s seed keys, with a full example, are in the Seed config section of its page: for example GitHub, Stripe, or Slack. Providers lists every page. Every provider’s section goes under its service name at the top level of the config file, next to the shared keys on this page. Next to the seed keys, a provider’s section takes port, baseUrl, faults, and version, the provider version range to run (such as "^0.14"). start pins the exact version in baseline.lock; see Provider versions.

OIDC signing keys

Google, Microsoft, Apple, Okta, Clerk, and Auth0 sign ID tokens (and Clerk session tokens and Auth0 access tokens) with an RS256 key kept in emulator state under <service>.signing_keys. The key is generated on first use and stays with that state, so tokens keep verifying after a snapshot restore or a restart from adapter persistence. A reset generates a new key unless one is pinned. Each provider keeps a fixed kid: baseline-google-1, baseline-microsoft-1, baseline-apple-1, baseline-okta-1, baseline-clerk-1, and baseline-auth0-1. To pin the key, for example to verify tokens in your own tests with a known public key, set signing_key.private_jwk to an RSA private JWK. The option is the same under google, microsoft, apple, okta, clerk, and auth0:
The JWKS endpoint serves only the public part. A kid on the JWK replaces the provider default. Generate a key with:
When persisted state is restored, the saved key is used and the seed is not applied again until the state is reset.

Custom emulators

Custom definitions created with defineEmulator from @deploybaseline/cli work in the CLI and in in-process tests. The same definition provides typed state, HTTP handlers, reset, versioned snapshots, and optional inspection/persistence. See Custom emulators for the quickstart, API reference, and complete lifecycle semantics.

Provider packages

A service can run a provider package from npm that is not built in, in its own process like the built-in providers. Name it with provider: and give it a port:
It runs only once you trust that version: start asks in a terminal and pins it in baseline.lock, and CI runs it once the committed lockfile trusts it (npx @deploybaseline/cli trust billing). See Provider packages.

Known limits

  • A watch reload (--watch) restarts every provider, including those whose config did not change.
  • --single-port is refused (see Single port).
  • The control token and host checks cover the /_baseline/* routes only. Service routes and WebSocket upgrades (Slack Socket Mode, the Discord Gateway) answer any host, so with --host 0.0.0.0 they are reachable from the network.
  • createEmulator runs built-in services and custom emulators. It does not take provider: packages, and it does not apply shared identities:.