Skip to content
Docs

BifrostQL Configuration Reference

This configuration reference covers every BifrostQL setting. BifrostQL is configured through appsettings.json (or any ASP.NET Core configuration source). All settings live under the BifrostQL key.

{
"ConnectionStrings": {
"bifrost": "Server=localhost;Database=mydb;User Id=sa;Password=xxx"
},
"BifrostQL": {
"Path": "/graphql",
"Playground": "/graphiql",
"DisableAuth": false,
"Provider": "sqlserver",
"Metadata": [
"dbo.sys* { visibility: hidden; }",
"dbo.*|has(tenant_id) { tenant-filter: tenant_id; }",
"dbo.orders { soft-delete: deleted_at; soft-delete-by: deleted_by_user_id; delete-type: soft; }",
"dbo.*.createdOn { populate: created-on; update: none; }",
"dbo.*.updatedOn { populate: updated-on; update: none; }",
":root { raw-sql: disabled; generic-table: disabled; }"
]
},
"JwtSettings": {
"Authority": "https://your-idp.com",
"Audience": "your-api"
}
}
Setting Type Default Description
ConnectionStrings:bifrost string required Database connection string
BifrostQL:Path string /graphql GraphQL endpoint path
BifrostQL:Playground string / GraphiQL playground path. The example above overrides it to /graphiql.
BifrostQL:DisableAuth bool false Disable authentication checks
BifrostQL:Provider string sqlserver Database provider: sqlserver, postgres, mysql, sqlite
BifrostQL:Metadata string[] [] Array of metadata configuration rules
BifrostQL:Http3:Enabled bool false Enable HTTP/3 (QUIC) support
BifrostQL:Http3:HttpsPort int 5001 HTTPS port for HTTP/3

Metadata rules use a CSS-like selector syntax to target tables and columns. Each rule has a selector and a block of properties:

"selector { property: value; property: value; }"

The rule string is parsed structurally, not delimiter-agnostically:

  • { and } delimit the property block. The block runs from the first { to the last }. Selectors may not contain braces, but property values may — a value like policy-row-scope: user_id = {user_id} is preserved verbatim, so {placeholder} expressions are legal.
  • ; separates one property from the next inside the block. A value cannot contain a literal ;.
  • : separates a property key from its value. Only the first : splits; the rest of the line is the value, so values may contain : (e.g. many-to-many: Target:Junction, computed-sql: name:Type:expr).
  • , separates multiple selectors sharing one property block.
  • * is a wildcard in selectors. Every other character in a selector is matched literally, including regex metacharacters (+, (, ), . within a name), so a table named data(2024) is matched as written.

Malformed rules fail fast at load: a missing brace, an empty selector, a property with no :, an empty key, a duplicate key, or an unbalanced backtick throws an ArgumentException naming the offending rule rather than being silently ignored.

A value that itself needs ; (its only otherwise-reserved character) can be wrapped in backticks. The interior is taken verbatim — no ; splitting, no trimming — so a complex value keeps its natural form:

"dbo.orders { computed-sql: `full:String:{first} + {last}; other:String:{a} || {b}` }"

Without the backticks the internal ; would be read as a property separator and tear the value apart. For very large expressions, keep the rule in its own JSON file and rely on standard IConfiguration layering rather than inlining a huge string.

Most properties are last-writer-wins when several rules target the same element. Two exceptions accumulate instead, comma-joined across rules, because they carry list values: join and many-to-many. For example, two many-to-many rules on dbo.posts combine into a single Roles:UserRoles, Tags:PostTags declaration.

Keys are kebab-case. A few validation keys have a legacy glued spelling still stored internally (minlength, maxlength); the kebab forms min-length and max-length are accepted and normalized to them, so a config can use kebab-case consistently.

Pattern Matches
dbo.orders The orders table in the dbo schema
dbo.orders.total The total column on dbo.orders
dbo.* All tables in the dbo schema
dbo.*.createdOn The createdOn column on every table in dbo
*.* All tables in all schemas
dbo.sys* Tables starting with sys in dbo
dbo.*.__* Columns starting with __ on all dbo tables
dbo.*|has(tenant_id) Tables in dbo that have a tenant_id column
Property Values Applies to Description
tenant-filter column name table Enable tenant isolation on this column
tenant-context-key claim key model User-context key for tenant ID (default: tenant_id)
auto-filter column:claim[,column:claim] table Inject filters from arbitrary user-context claims
auto-filter-bypass-role role name model Role that bypasses auto-filter rules
soft-delete column name table Soft-delete timestamp column
soft-delete-by column name table Column recording who deleted
delete-type soft table Mark table for soft-delete behavior
populate see below column Auto-populate from user context
update none column Make column read-only for updates
visibility hidden table/column Hide from GraphQL schema
label column name table Display label column
join join declaration table/column Declare explicit relationships
many-to-many TargetTable:JunctionTable table Declare a many-to-many relationship (accumulates across rules)
auto-join true/false model/table Enable automatic join inference
foreign-joins true/false model Enable FK-based join inference
default-limit number model/table Default page size
max-query-rows number model Server-side row ceiling for the GraphQL surface. Clamps the no-limit sentinel (limit: -1), any explicit limit above it, and the default 100-row window an unspecified limit resolves to — on the root query, on nested collections (per parent for paged collections), and on the group window of <table>Aggregate. Default 10000; a non-positive value fails model load
de-pluralize true/false model De-pluralize table names in schema
batch-max-size number table Maximum batch mutation size
bulk-batch-threshold number table Batch size at which SQL Server, PostgreSQL, and MySQL batches switch to the set-based fast path (temp-table staging + set-based DML in one SQL-level transaction). Default 50; 0 or negative disables the fast path. Batches with hooks (approval, history, CDC), upserts, or state machines always use the per-row path; SQLite keeps the per-row path (a single in-process writer gains nothing from staging). Measured throughput: see Bulk Batch Performance
batch-duplicate-policy last-wins/reject table How a batch with multiple update/delete actions for one key resolves — always deterministically, in the pipeline, before any SQL. last-wins (default) collapses to the sequential net effect (collapsed intermediates never execute, so hooks/history never record them); reject refuses the batch with a clean error
filtered-update enabled table Opt-in for the updateWhere filtered set-update; without it the argument does not exist in the schema
filtered-update-max-affected number table Cap on rows one filtered update may affect (default 100); a COUNT precheck in the update’s own transaction throws and rolls back on breach
Property Values Applies to Description
raw-sql enabled/disabled model Expose _rawQuery(sql:, params:, timeout:)
raw-sql-role role name model Role required for _rawQuery (default: bifrost-raw-sql)
raw-sql-timeout seconds model Max raw SQL timeout
raw-sql-max-rows number model Max rows returned by raw SQL
generic-table enabled/disabled model Expose _table(name:, limit:, offset:, filter:)
generic-table-role role name model Role required for _table (default: bifrost-admin)
generic-table-max-rows number model Max rows returned by _table
generic-table-allowed comma list model Allow-list for generic table names
generic-table-denied comma list model Deny-list for generic table names
schema-prefix enabled/disabled model Prefix GraphQL table names with schema names
schema-prefix-default schema name model Schema left unprefixed when prefixing is enabled
schema-prefix-format format string model Custom schema prefix format
schema-display flat/prefix/field model Multi-schema presentation mode
schema-default schema name model Default schema for field-mode presentation
schema-excluded comma list model Schemas hidden from schema-field presentation
sp-include regex model Include matching stored procedures
sp-exclude regex model Exclude matching stored procedures
auto-detect-app disabled, wordpress, etc. model Control app-schema detection
app-schema detector name model Force a specific app-schema detector
detected-app detector name model Read-only detection result metadata
Property Values Applies to Description
eav-parent table name table Parent table for an EAV meta table
eav-fk column name table FK from EAV table to parent
eav-key column name table EAV attribute-name column
eav-value column name table EAV attribute-value column
file config string column Mark column as a file-storage column
file-storage config string column Legacy file-storage marker
storage config string model/table/column Storage bucket configuration
max-size bytes column Max file size
content-type-column column name column Column storing MIME type
file-name-column column name column Column storing original filename
accept MIME pattern column Accepted upload MIME types

Opts a table into the syndicated feed surface. A table is not published unless it sets feed-timestamp; feed metadata without that required opt-in is rejected at model load. The table must have one or more primary-key columns (composite keys are supported) so every item has a stable GUID and ordering identity.

Property Values Applies to Description
feed-timestamp date/time column name table Required opt-in. Existing date/time column used to order feed items
feed-title column name or {column} template table Required. Title source; referenced columns must exist and be unencrypted
feed-body column name table Required. Body source; the column must exist and be unencrypted
feed-link {column} template table Optional item-link template. Placeholders must name schema-derived, unencrypted columns

For example: dbo.posts { feed-timestamp: published_at; feed-title: {title}; feed-body: body; feed-link: /posts/{slug}; }. Malformed placeholders, unknown feed-* keys, empty configuration, and a timestamp that is missing or not date/time-typed all fail fast at model load.

Declares a chat schema over user-supplied tables — exactly one conversations table paired with exactly one messages table per model. Both tables must be published, and must not be change-history targets (they may themselves record history). See Chat over your tables.

Property Values Applies to Description
chat-conversations enabled table Mark the table as the chat conversations table
chat-title column name table Optional conversation title column (conversations table only)
chat-messages enabled table Mark the table as the chat messages table (requires the full column mapping below)
chat-role column name table Message role column; must be string-typed
chat-content column name table Message content column; must be string-typed
chat-conversation-fk column name table Column referencing the conversations table’s single-column primary key (via a declared FK or join rule)
chat-created-at column name table Message timestamp column; must be date/time-typed

Exposes a table to the chat LLM as a Claude tool. A table opts in with chat-connector, naming one or more connector types: explore (read/query), media (serve an image/file column), plan (gated writes). Any number of tables may be connectors. A connector table must be published (not visibility: hidden) and must not be a change-history target (it may itself record history). See Chat over your tables.

Property Values Applies to Description
chat-connector comma list of explore/media/plan table Connector types the table exposes; unknown tokens and empty values are rejected
chat-media-column column name table Image/file column a media connector serves (required with the media token). The serving mode is derived from the column type: binary-typed columns serve bytes, string-typed columns serve URLs
chat-media-vision enabled table Send the media content to the model as vision input (media token required)
chat-media-caption column name table Optional caption/alt-text column; must be string-typed (media token required)
chat-plan-operations comma list of insert/update/delete table Write allow-list for a plan connector (required with the plan token). delete is never implied — it must be listed explicitly. The table must have a primary key
chat-tool-description free text table Optional description feeding the generated Claude tool (any connector type); present-but-empty is rejected

Validation fails fast at model load: unknown type tokens or operations, media/plan keys without their type token, a media column that does not exist or is neither binary- nor string-typed, a non-string caption column, a plan connector on a keyless table, and any unrecognized chat-connector-*/chat-media-*/chat-plan-* key are all rejected before the first chat request. An explore connector needs no extra columns — any published table qualifies.

Opts tables into emitting insert/update/delete domain events through a transactional outbox, and (optionally) filters what a delivery sink receives. See Change Data Capture & Outbound Events for the concept and Emitting Change Events for the walkthrough. Every key below is defined in MetadataKeys.Cdc.

Property Values Applies to Description
emit-events comma list of insert/update/delete table Operations that emit an event. Presence of this key opts the table in; a subset is allowed
event-sink outbox table Durable sink. Only outbox is recognized; defaults to outbox when omitted
event-payload full/changed/keys table How much of the row is captured in payload. Defaults to full
outbox-table qualified table name model The transactional outbox table events are written to. Required once any table sets emit-events; the table must exist and carry the outbox column contract
webhook-secret comma list of secrets model HMAC signing secret(s) for the webhook sink. Multiple values enable zero-downtime secret rotation
subscription-tables comma list of qualified tables model Fail-closed delivery allow-list: an event delivers only if its aggregate is listed. Presence of any subscription-* key activates the subscription; an empty allow-list delivers nothing
subscription-tenant tenant id model Binds the subscription to one tenant — only outbox rows whose tenant equals it are delivered; null/unknown-tenant rows never are
subscription-redact comma list of column names model Columns stripped from the payload before any sink sees it. Primary-key columns are never stripped

With no subscription-* key every event is delivered (deliver-all). Unknown subscription-* keys and a subscription-tables entry naming a non-existent table fail at model load. The delivery-sink host settings (Cdc:WebhookUrl, Cdc:NatsUrl) live in application configuration, not metadata — see the guide.

{
"Metadata": [
"dbo.orders { emit-events: insert,update,delete; event-payload: changed }",
"dbo.widgets { emit-events: insert,update }",
":root { outbox-table: dbo.__outbox; webhook-secret: old-secret,new-secret; subscription-tables: dbo.orders, dbo.widgets; subscription-tenant: acme; subscription-redact: ssn,card_number }"
]
}
Value Description
created-by User audit key (on insert only)
updated-by User audit key (on insert and update)
created-on Current timestamp (on insert only)
updated-on Current timestamp (on insert and update)
deleted-on Current timestamp (on soft-delete)
deleted-by User audit key (on soft-delete)

Rules are applied in order. Later rules override earlier ones for the same target. Use broad rules first, then specific overrides:

{
"Metadata": [
"dbo.* { de-pluralize: true; default-limit: 50; }",
"dbo.audit_log { de-pluralize: false; default-limit: 100; }"
]
}
Server=localhost;Database=mydb;User Id=sa;Password=xxx

Modern SqlClient encrypts this connection by default and validates the server’s certificate. See TrustServerCertificate before adding that keyword.

TrustServerCertificate=True tells the client to accept whatever certificate the server presents — expired, self-signed, or issued by anyone at all — without checking it. The connection is still encrypted, but encryption without identity is not a secure channel: anyone able to sit on the network path to the database can terminate the TLS session themselves, read the credentials in the connection string, and read every query and result that follows. Nothing in the client will report that this happened.

It is legitimate in exactly one situation: a server whose certificate you already trust but your machine’s trust store cannot verify — typically a local development container, or an internal server using a self-signed or internally-issued certificate — reached over a network path you control.

It is not a fix for a certificate error in production. There the answer is to install a certificate the client’s trust store accepts, or to add your internal CA to that store.

If you do need it, set it deliberately and per-connection rather than carrying it in a shared default:

Server=dev-sql;Database=mydb;User Id=sa;Password=xxx

The bifrost CLI exposes the same waiver as --trust-server-certificate, and the desktop app as a per-entry setting. Both warn while it is in force.

Host=localhost;Port=5432;Database=mydb;Username=postgres;Password=xxx
Server=localhost;Port=3306;Database=mydb;User=root;Password=xxx
Data Source=path/to/database.db