Skip to content

Configuration reference

Reference · Configuration

askdb.config.ts holds every setting the CLI, Studio, the HTTP API and createAskDb() read. Environment variables reach it through env() calls in the file. This page lists each key. To load the config in your own code, see the @askdb/config API.

askdb init scaffolds this file. It uses defineConfig for type safety:

import { defineConfig, env } from "@askdb/config";
export default defineConfig({
ai: {
provider: "openai",
providerConfig: {
openai: { apiKey: env("OPENAI_API_KEY") },
},
language: { model: env("OPENAI_MODEL") },
embedding: { model: "text-embedding-3-small" },
},
introspection: {
provider: "postgres",
providerConfig: {
postgres: { databaseUrl: env("DATABASE_URL") },
},
outputDir: "./my-app.schema",
},
rag: {
embedder: "ai",
store: "pgvector",
storeConfig: {
pgvector: { databaseUrl: env("PGVECTOR_URL") },
},
},
logging: {
level: "info",
},
});

env(name) and requiredEnv(name), both from @askdb/config, read an environment variable when the config file loads, and trim it:

  • env() returns undefined when the variable is missing or blank, so the field stays unset and gets its default.
  • requiredEnv() throws when the variable is missing or blank. Every command loads the whole config, so the variable must be set even for a command that doesn’t use it, such as an API key when you run askdb introspect.

defineConfig types the object as AskDbConfig, so your editor checks it as you write. A plain export default { … } loads too: AskDB runs it through defineConfig when it loads the file, so both forms get the same checks.

FieldRequiredWhat it configures
aiyesProvider connections (provider, providerConfig), the language model (language) and the embedding model (embedding). See The ai block and Bring your own model.
introspectionyesThe engine askdb introspect reads (provider), its connection (providerConfig), the output directory (outputDir) and the schemas to read (schemas). See The introspection block.
dialectnoOverrides the NL-to-SQL dialect: postgres, cockroachdb, mysql, mariadb, sqlite or sqlserver. When unset, it’s inferred from the schema artifact’s recorded provider. See dialect.
ragnoExperimental. The embedder (mock, or ai for the ai.embedding model), the vector store (file, memory, pgvector) and the store’s options. Omit it when you don’t use retrieval: AskDB then uses the mock embedder and the in-memory store. See The rag block.
modesnoaskdbMode (schema_only default, bounded_results) and omitSensitiveFromPrompt (default false). Both are floors: a CLI flag, a client call, or an HTTP request can tighten them, never loosen them. See modes and host.
hostnoschemaPath or schemaJson: the default schema artifact for createAskDb and the HTTP API. See modes and host.
loggingnoThe level, log file and stdout copy of the structured logs from the CLI and the HTTP API server, and the CLI’s correlation ID. See logging and dev.
devnomockSql: fixed SQL in place of the model call, for tests. See logging and dev.
studionoExperimental. Studio’s listen address and the Query Playground’s execute block. See The studio block.
httpApinoThe HTTP API server’s listen address, schema override and request timeout. See The httpApi block.

A config without ai or introspection fails to load, with an error that names the file and the missing block. That includes a config you only use for askdb introspect: it still needs an ai block.

The sections below list every field of each block, with its type and default. Required in the Default column means the field must be set whenever its block is present, or under the condition the column names; every other field is optional. A config that sets a field AskDB no longer has fails to load (see Removed keys).

AskDB checks each field’s type when the config loads. A value of another type fails to load, with an error that names the file and the field, such as AskDB config at /app/askdb.config.ts: modes.omitSensitiveFromPrompt must be a boolean (got "false"). A boolean field takes only true or false, because the string "false" is truthy. A number field also takes a string that holds the number, such as "8080", since env() returns a string. A field set to undefined, as an unset env() read gives, takes its default, and so does a string field set to null.

ai separates how to reach a provider from which model to use for what: providerConfig holds the connections, and language and embedding each pick a model.

FieldTypeDefaultNotes
providerstringnoneThe default provider for both sections: openai, azure, foundry, anthropic, google, gateway, or a custom provider id.
providerConfigobjectnoneProvider connections, keyed by provider id: keys, endpoints, Azure resources, API versions. No model names. See ai.providerConfig.
languageobjectai.provider and its default modelThe language model AskDB writes SQL and enrichment suggestions with. See ai.language.
embeddingobjectnoneThe embedding model behind rag.embedder: "ai". See ai.embedding.

A section’s provider is its own provider, else ai.provider. Its connection is its own connection, else the connection named "default" within that provider. Loading fails when a section has no provider, names a connection that doesn’t exist, or uses a built-in provider with no connection at all. Each section only ever receives its own connection’s settings, so a key configured for one provider is never sent to another.

Anthropic for SQL, OpenAI for embeddings:

export default defineConfig({
ai: {
provider: "anthropic",
providerConfig: {
anthropic: { apiKey: env("ANTHROPIC_API_KEY") },
openai: { apiKey: env("OPENAI_API_KEY") },
},
language: { model: "claude-sonnet-4-6" },
embedding: { provider: "openai", model: "text-embedding-3-small" },
},
rag: { embedder: "ai", store: "file", storeConfig: { file: {} } },
// ...
});

Two Azure resources, one for each model:

export default defineConfig({
ai: {
provider: "azure",
providerConfig: {
azure: [
{ resourceName: "eastus-chat", apiKey: env("AZURE_CHAT_KEY") }, // name: "default"
{ name: "westus", resourceName: "westus-embed", apiKey: env("AZURE_EMBED_KEY") },
],
},
language: { model: "my-gpt-4o-deployment", modelFamily: "gpt-4o" },
embedding: { connection: "westus", model: "my-embedding-deployment", dimensions: 1536 },
},
rag: { embedder: "ai", store: "pgvector", storeConfig: { pgvector: { databaseUrl: env("PGVECTOR_URL") } } },
// ...
});

Keyed by provider id. Each value is one connection, or a list of connections when a provider needs more than one. Every connection field is a string with no default.

Provider keyConnection fields
openaiapiKey, baseUrl (any OpenAI-compatible endpoint)
azure, foundryapiKey, secondaryApiKey, resourceName (the subdomain of https://<name>.openai.azure.com), baseUrl (a full endpoint URL; overrides resourceName), apiVersion. One of resourceName or baseUrl is required.
anthropicapiKey, baseUrl
googleapiKey, baseUrl
gatewayapiKey, baseUrl (Vercel AI Gateway)
any other idapiKey, baseUrl, for a custom provider

Every connection also takes name, which defaults to "default" and must be unique within its provider. A list needs a "default" entry unless every section that uses the provider sets connection.

A baseUrl can’t hold a username or password: a config that sets one on any connection, used or not, fails to load, with an error that names the key and doesn’t repeat the URL. fetch refuses such a URL anyway. The same goes for OPENAI_BASE_URL and ANTHROPIC_BASE_URL in the environment, which the OpenAI and Anthropic SDKs read when the connection sets no baseUrl. Put the key in apiKey. To send other credentials, such as a proxy’s, build the model in your own code with the AI SDK provider’s headers option and pass it to ask() as model (see Bring your own model). The askdb CLI, Studio and the HTTP server can’t send extra headers.

FieldTypeDefaultNotes
providerstringai.provider
connectionstring"default"A connection name in ai.providerConfig.<provider>.
modelstringThe provider’s default language modelopenai: gpt-4o-mini. anthropic: claude-sonnet-4-6. google: gemini-2.0-flash. gateway: openai/gpt-4o-mini; an id without its <upstream>/ prefix is rejected when the model is built. azure and foundry: the deployment name, default gpt-4o-mini. A custom provider has no default.
modelFamilystringnoneAzure and Foundry: the underlying model id when model is a deployment name. See Azure deployment names.
reasoningobjectnoneSee Reasoning effort.

Read only when rag.embedder is "ai"; ignored for "mock". Setting it in a config with no rag block is an error.

FieldTypeDefaultNotes
providerstringai.providerAnthropic has no embeddings API, so a config whose ai.embedding provider resolves to anthropic fails to load. Set provider (for example "openai") and give it a connection.
connectionstring"default"A connection name in ai.providerConfig.<provider>.
modelstringRequired with rag.embedder: "ai"AskDB has no default embedding model. On Azure, the embedding deployment name. On the AI Gateway, an <upstream>/<model> id such as openai/text-embedding-3-small.
dimensionsnumber or stringThe model’s own widthA positive integer, or a string holding one: the vector size to request from the provider, for models that let you choose one (OpenAI’s text-embedding-3 models, Gemini’s; on the AI Gateway, openai/… and google/… models, while another upstream’s model refuses it). Unset, AskDB assumes nothing: it uses the width the model returns, which it learns when it first builds the index, so the pgvector store doesn’t need it either.

Changing the ai.embedding provider, model or dimensions changes the index’s embedder id: Studio then reports the RAG index as stale and asks you to reindex before using it.

ai.language.reasoning is an optional, provider-portable knob for how much reasoning/thinking effort AskDB’s model calls use. Leave it unset to keep the provider/model’s own default (AskDB sends no reasoning options at all — this is the default, unchanged behavior).

export default defineConfig({
ai: {
provider: "openai",
providerConfig: { openai: { apiKey: env("OPENAI_API_KEY") } },
language: {
reasoning: {
effort: "medium", // global default for every AskDB model call
nlToSql: "high", // override: accuracy-sensitive NL→SQL generation
enrichment: "low", // override: non-critical enrichment suggestions
},
},
},
// ...
});
FieldTypeDefaultNotes
effort"minimal" | "low" | "medium" | "high"noneGlobal default for every AskDB model call.
nlToSqlSame as efforteffortOverrides effort for NL→SQL generation calls (ask()). Typically left unset (provider default) or "medium"/"high" — this path is accuracy-sensitive.
enrichmentSame as efforteffortOverrides effort for enrichment/suggestion calls (suggestEnrichment()). Safe to bias toward "low"/"minimal" for latency and cost — these are non-critical.

AskDB maps each value to the provider’s own setting, and sends nothing to a model that doesn’t support reasoning tuning, even when reasoning is set. Bring your own model lists each provider’s setting.

@askdb/client’s createAskDb applies ai.language.reasoning to its ask() calls with no extra code. Bring your own model shows how to set the effort per client or per call, and how to apply it to a model you build yourself.

Azure identifies models by deployment name (e.g. askdb-reporting), which may not match the underlying model id AskDB uses to detect reasoning support. Set ai.language.modelFamily to the real model id — e.g. "gpt-5" or "o3-mini" — when your deployment name doesn’t already look like one.

introspection is required. askdb introspect reads it, and its flags override each field for one run (see CLI reference). providerConfig can hold connections for several engines; askdb introspect uses the one provider names.

FieldTypeDefaultNotes
provider"postgres" | "mysql" | "sqlite" | "sqlserver" | "prisma"RequiredThe engine to introspect. MariaDB uses mysql.
providerConfig.postgres.databaseUrlstringnonePostgres connection URL. Without it, pass askdb introspect --url.
providerConfig.mysql.databaseUrlstringnoneMySQL or MariaDB connection URL, such as mysql://user:pass@host:3306/database. Without it, pass --url.
providerConfig.sqlite.filestringnonePath to an existing .db or .sqlite file, which introspection opens read-only. :memory: doesn’t work: better-sqlite3 can’t open an in-memory database read-only. Without it, pass --url <path>.
providerConfig.sqlserver.databaseUrlstringnoneSQL Server connection URL: an mssql URI or a Server=…; connection string. Without it, pass --url.
providerConfig.prisma.schemaPathstringprisma/schema.prisma, else schema.prisma, in the project rootA schema.prisma file, or a directory of .prisma files. --prisma-schema overrides it.
outputDirstring./askdb/Where askdb introspect writes the schema artifact. --out overrides it. askdb ask and Studio read the artifact from here.
schemasstring[]Postgres and SQL Server: every non-system schema. MySQL and MariaDB: the URL’s database.The schemas to introspect, such as ["public", "sales"]. --schemas overrides it. On MySQL and MariaDB these are databases: each becomes its own namespace, and foreign keys between them are kept.
FieldTypeDefaultNotes
dialect"postgres" | "cockroachdb" | "mysql" | "mariadb" | "sqlite" | "sqlserver"The schema artifact’s recorded provider, else postgresThe NL-to-SQL dialect createAskDb uses, and so the CLI, Studio and the HTTP API. When it’s unset and the artifact records a provider with no built-in dialect, they refuse with DialectNotSupportedError rather than guess. askdb-http applies it to its own schema only: a request schemaJson uses the artifact’s recorded provider. @askdb/core’s ask() reads no config: pass dialect to it yourself.

The dialect normally resolves from the schema artifact (the engine recorded at introspection time). Set dialect only when that inference is wrong — for example, a Prisma schema that declares provider = "postgresql" while your runtime target is a different engine:

export default defineConfig({
// ...
dialect: "mysql",
});

An artifact introspected from MariaDB records mysql, so set dialect: "mariadb" to use the MariaDB dialect.

cockroachdb is a built-in dialect: it reuses the Postgres rules and adds CockroachDB’s reserved words. It sits outside the 1.0 support claim, because AskDB’s end-to-end tests don’t cover it. The 1.0 claim covers PostgreSQL, MySQL, MariaDB, SQLite and SQL Server.

Experimental: rag follows the 0.x @askdb/rag package, so a minor release of @askdb/config may change it (ADR 0030).

The block is optional. Without it, AskDB behaves as if rag: { embedder: "mock", store: "memory", storeConfig: {} } were set: Studio’s RAG page uses the in-memory store and the mock embedder, and getAskDbRuntimeConfig().rag.store is "memory". When the block is present, embedder, store and storeConfig are required. A config that sets ai.embedding but has no rag block fails to load, since nothing would use that model: add rag: { embedder: "ai", … }, or remove ai.embedding.

FieldTypeDefaultNotes
embedder"mock" | "ai"Required"mock" is a deterministic lexical embedder that needs no key. Its vectors are 64 wide unless askdb rag --dimensions asks for another width. "ai" is the ai.embedding model.
store"file" | "memory" | "pgvector"RequiredThe vector store.
storeConfigobjectRequiredThe selected store’s options, below. The file and pgvector stores need their branch, even an empty one (storeConfig: { file: {} }). The type requires storeConfig with memory too, but a JavaScript config can leave it out.
FieldTypeDefaultNotes
file.basePathstring<schema dir>/schema: ./askdb/schema with the default outputDirPath prefix of the file store. It writes <basePath>.embeddings.bin and <basePath>.embeddings.json. A relative path resolves against the working directory.
memory{}noneThe in-memory store has no options.
pgvector.databaseUrlstringRequired with store: "pgvector"Connection URL of the Postgres database with pgvector.
pgvector.tablestringaskdb_rag_chunksThe table that holds the chunks and their vectors.
pgvector.indexStrategy"hnsw" | "ivfflat" | "none""hnsw"The index askdb rag setup-store, askdb rag index and Studio create. "none" means sequential scans. With store: "pgvector", another value fails to load.

With embedder: "ai", the pgvector table is created at the width the embedding model returns, learned when the index is first built (with ai.embedding.dimensions set, that’s the size requested). An existing table of another width is refused, not overwritten. With embedder: "mock", the table is 64 wide unless askdb rag --dimensions asks for another width. The file store writes its files after every change.

FieldTypeDefaultNotes
modes.askdbMode"schema_only" | "bounded_results""schema_only"The loosest mode a run gets (see below). bounded_results is reserved: today it runs exactly like schema_only.
modes.omitSensitiveFromPromptbooleanfalseLeaves sensitive columns, and schema text that names one, out of the prompt. A run can’t turn it off. See Privacy model.
host.schemaPathstringnoneThe schema artifact createAskDb and the HTTP API load when a call names none. askdb ask reads introspection.outputDir instead.
host.schemaJsonstringnoneThe schema artifact as inline JSON, for environments without a file system path. When both are set, schemaJson wins.
export default defineConfig({
// ...
modes: {
askdbMode: "schema_only", // default
omitSensitiveFromPrompt: true, // strict sensitive-column handling
},
host: {
schemaPath: "./my-app.schema", // default artifact for embedding + HTTP API
},
});

modes.askdbMode is the loosest mode a run gets, on every surface that reads config: the CLI, @askdb/client, and the HTTP API. A run that names no mode uses it. A run can ask for schema_only to tighten it. A run that asks for bounded_results while the config mode is schema_only runs schema_only, with no error; the askdb.pipeline.mode log line records the mode that ran.

modes.omitSensitiveFromPrompt: true works the same way: a run can’t turn it off. @askdb/core’s ask() reads no config, so pass mode to it yourself.

logging sets the defaults for the structured JSON logs of the CLI (askdb ask, askdb introspect, askdb rag index and askdb rag query) and the HTTP API server. Studio doesn’t read it. Logs go to stderr, and each CLI flag overrides its field for one run (see Shared logging flags).

FieldTypeDefaultNotes
logging.level"fatal" | "error" | "warn" | "info" | "debug" | "trace" | "silent"CLI: "silent", or "info" with -v, --log-file or --log-stdout. HTTP server: "info".Another value fails to load. A logFile or logStdout in config doesn’t raise the CLI’s level: set level too.
logging.logFilestringnoneAlso appends the logs to this file. Parent directories are created. The file stays open for the life of the process: rotate it with copytruncate, or restart the process after a rotate.
logging.logStdoutbooleanfalseAlso copies the logs to stdout.
logging.correlationIdstringA new UUID per runCLI only: the ID on every log line of a run. --correlation-id overrides it. The HTTP server uses each request’s x-correlation-id header instead, else a new UUID.
dev.mockSqlstringnoneSQL that askdb ask, createAskDb and the HTTP API use in place of the model call, for tests and local development.

Experimental: studio follows the 0.x @askdb/studio package, so a minor release of @askdb/config may change it (ADR 0030).

FieldTypeDefaultNotes
listen.hoststring"127.0.0.1"Studio’s bind address. askdb studio --host overrides it.
listen.portnumber5556Studio’s port, an integer from 1 to 65535. --port overrides it. Another value fails when Studio starts.
executeobjectnoneRunning SQL from the Playground. See Studio execute configuration.

The studio.execute block configures the Playground’s live execution. It’s off by default: set enabled: true and give Studio its own connection, ideally for a read-only database role. Enable Studio execute shows a config.

FieldTypeDefaultNotes
studio.execute.enabledbooleanfalseWhile off, POST /api/execute returns 403 and the Playground hides Execute.
studio.execute.provider"postgres" | "mysql" | "sqlite" | "sqlserver"The active introspection provider, or "postgres"See the resolution order below.
studio.execute.databaseUrlstringnoneConnection URL for Postgres, MySQL and SQL Server. See useIntrospectionConnection.
studio.execute.filestringnoneSQLite file path. See useIntrospectionConnection.
studio.execute.useIntrospectionConnectionbooleanfalseWhen true, reuse the introspection connection if databaseUrl / file is unset.
studio.execute.timeoutMsnumber30000Per-query timeout in milliseconds, a positive integer. Not enforced for SQLite.
studio.execute.maxRowsnumber500Row cap, a positive integer. Results with more rows are truncated.

Provider resolution order:

  1. studio.execute.provider. Any other value fails to load, so a typo can’t point Execute at another engine.
  2. The active introspection.provider when it is a live engine (postgres, mysql, sqlite, sqlserver).
  3. "postgres" for backward compatibility.

Connection resolution (per provider):

  • Postgres, MySQL, SQL Server: studio.execute.databaseUrl → the introspection URL for the same engine, only when useIntrospectionConnection: true.
  • SQLite: studio.execute.file → the introspection SQLite file, only when useIntrospectionConnection: true.

Studio doesn’t fall back to your introspection credentials on its own: give execute the connection of a role that only reads. Studio reference — Execute guards describes the read-only transaction, timeout, and row cap applied to every query.

Each execute provider requires its optional peer driver package (pg, mysql2, better-sqlite3, or mssql). See Enable Studio execute — Install the driver for install instructions.

httpApi configures the askdb-http server and createAskDbHttpServer(). An askdb-http flag or a createAskDbHttpServer() option overrides listen (see HTTP API → Server config).

FieldTypeDefaultNotes
listen.portnumber3000The server’s port: an integer from 0 to 65535. NaN counts as unset. Another value fails to load.
listen.hoststring"127.0.0.1"The bind address. Bind to 0.0.0.0 only behind a gateway.
allowSchemaOverridebooleanfalseAccept a per-request schemaJson on POST /ask. An override is a schema artifact the server parses and puts into the prompt through its model key, so enable it only for tests or trusted multi-schema deployments, on @askdb/http-api 1.0.0-beta.48 or later (GHSA-qqx9-qpw2-wcqg).
requestTimeoutMsnumber60000Aborts the model call of a POST /ask request after this many milliseconds: a positive integer, at most 2147483647. Another value fails to load.

A platform that sets the port in a PORT variable needs it converted to a number: see Deploy as an HTTP service.

A config that sets a key AskDB removed before 1.0 fails to load. The askdb.config: error names each such key and its replacement, and never repeats a value. A key counts as set whatever its value, so a key that reads an unset variable with env("…") fails too. Upgrade AskDB shows each removed key before and after.

AskDB loads .env first, then your askdb.config.*. You choose the variable names: they’re whatever you pass to env("…") in your config. This table lists the names askdb init writes by default, and Studio’s setup wizard uses the same ones. Rename any of them in your config and .env together.

VariableConfig fieldWritten for
DATABASE_URLintrospection.providerConfig.<engine>.databaseUrl; with --studio-execute, studio.execute.databaseUrl too--database postgres, mysql or sqlserver. --connection-env gives another name.
SQLITE_FILEintrospection.providerConfig.sqlite.file--database sqlite, unless --sqlite-file gives a path or another name. Studio’s setup wizard asks for the file’s path instead.
PGVECTOR_URLrag.storeConfig.pgvector.databaseUrl--rag-store pgvector. --pgvector-env gives another name.
OPENAI_API_KEY, OPENAI_MODELai.providerConfig.openai.apiKey, ai.language.model--ai-provider openai, the default.
ANTHROPIC_API_KEY, ANTHROPIC_MODELai.providerConfig.anthropic.apiKey, ai.language.model--ai-provider anthropic.
GOOGLE_GENERATIVE_AI_API_KEY, GOOGLE_AI_MODELai.providerConfig.google.apiKey, ai.language.model--ai-provider google.
AZURE_OPENAI_API_KEY, AZURE_RESOURCE_NAME, AZURE_OPENAI_DEPLOYMENTai.providerConfig.azure.apiKey and .resourceName (foundry in place of azure for Foundry), and ai.language.model, your deployment name--ai-provider azure or foundry.
AI_GATEWAY_API_KEY, ASKDB_AI_MODELai.providerConfig.gateway.apiKey, ai.language.model--ai-provider gateway.

--ai-key-env and --ai-model-env give the AI variables other names. A model variable that’s unset leaves ai.language.model to the provider’s default (see ai.language).

Every other setting, such as the output directory, a mode or the log level, is a field in askdb.config.ts, which you can bind to a variable with env("…") too. AskDB reads one variable of its own from the environment, ASKDB_DEBUG, which prints stack traces (see Fix common errors). An ASKDB_* variable you set in the shell does nothing unless your config reads it with env("…").

ai.provider, and a section’s provider, take any id, not only the built-in ones. Give a custom provider’s connection its id as the key in ai.providerConfig (apiKey and baseUrl) and its model in ai.language.model. It works only where an adapter is registered under that id, which takes your own code: Bring your own model shows how, and when the openai provider with a baseUrl is enough.

The CLI, Studio and the HTTP API load askdb.config.* from the directory they start in, and look nowhere else. bootstrapAskDbEnv() loads it in your own code, and the @askdb/config API lists the file names it tries, in order.

For any given setting, the precedence (highest wins) is:

  1. Per-call arguments — CLI flags (--schema, --log-level, …) and ask() options.
  2. The askdb.config.* file — the single source of truth for the runtime snapshot.
  3. Built-in defaults applied for optional fields (e.g. outputDir → ./askdb/).

The two modes settings are the exception. They are floors, not defaults: a per-call --mode, mode or omit setting can tighten the config value, never loosen it. See modes and host.

Environment variables don’t override the config file from the outside — they flow through it: every env("VAR") call in the config reads the environment when the file loads. .env is loaded first (without overwriting variables already set in the real environment), so the per-environment override story is: system env beats .env, and whatever env() reads lands in the snapshot. This still lets you ship one config file and vary it per environment — just route each tunable through env().