sqlib Docs HomeGet startedGitHub
Reference

Feature flags

Twenty boolean flags decide which sections of the product a build offers. A flag is read on the server, where it decides whether routes are registered, and again in the browser build, where it decides whether a section and the controls leading to it are drawn. The two read separate environments, so a deployment sets both.

The list, the environment variable names and the defaults live in packages/types/src/featureFlags.ts, which both halves import.

Values

A flag's value is parsed leniently, case-insensitively, after trimming:

The flags

Key Environment variable Default What is absent when it is off
notebook FEATURE_NOTEBOOK on The Notebook rail section and the links into it. /notebook stays reachable by URL
queries FEATURE_QUERIES on The Query rail section; the /queries, /execute and /sparql routes, and /detect-inputs / /detect-outputs. The Notebook has its own flag above
queryGroups FEATURE_QUERY_GROUPS on The Groups rail section; the /query-groups routes
rulesSuite FEATURE_RULES_SUITE on The Rules rail section; the /rules, /data-blocks and /rule-sets routes
benchmarks FEATURE_BENCHMARKS on The Bench rail section; the /benchmark-experiments routes
tests FEATURE_TESTS on The Tests rail section, the Tests tab on record pages, the run bar's "create test" target and the rules screen's "Save as test"; the /tests routes
dataGraphs FEATURE_DATA_GRAPHS on The Graphs rail section and the doors into it. The /data-graphs routes stay registered
tupleSets FEATURE_TUPLE_SETS on The Tuples rail section and the doors into it. The /tuple-sets routes stay registered
argumentSets FEATURE_ARGUMENT_SETS on The Argument sets rail section and the doors into it. The /argument-sets routes stay registered while queries, queryGroups or the /tests routes are on
build FEATURE_BUILD on The Build rail section and the links into it. /build stays reachable by URL, and what it shows still follows the section flags
etl FEATURE_ETL off The /etl-jobs routes, and the startup writability check on the ETL output directory
backends FEATURE_BACKENDS on The Backends rail section. The /backends routes stay registered unconditionally
settings FEATURE_SETTINGS on The settings entry in the rail
rulesAllowInvalidSave FEATURE_RULES_ALLOW_INVALID_SAVE off The ability to save a rule, data block or rule set version that does not parse. With it off, allowInvalidSave: true in a request body has no effect
ruleAggregates FEATURE_RULE_AGGREGATES off SRL GROUP aggregation, until w3c/data-shapes#1347 is merged — see below
playgroundQueries FEATURE_PLAYGROUND_QUERIES on The unsaved query playground, which runs through /execute; it has no route of its own
playgroundRules FEATURE_PLAYGROUND_RULES on The unsaved rules playground; POST /playground/rules/execute
playgroundEtl FEATURE_PLAYGROUND_ETL off The ETL rail section; POST /playground/etl/execute
assistant FEATURE_ASSISTANT off The in-app build assistant; the /assistant routes

Route-level refusal is a 404 Not Found: a request to a route behind a flag that is off reaches nothing, which is the same answer a route that was never added gives.

Why some flags default off

The off-by-default flags are off for stated reasons, not by oversight.

etl and playgroundEtl. Both ETL surfaces accept arbitrary DuckDB SQL. That is a read primitive over the host filesystem, and with the httpfs extension loaded it is an outbound-request primitive as well. ETL is an operator flow that someone sets up deliberately, so it should not appear on a deployment because that deployment was upgraded. See the ETL guide.

assistant. The in-app assistant is unauthenticated, it calls a paid provider, and it can reach a configured backend. See the security model.

The assistant's provider SDK (ai, @ai-sdk/*) is an optional dependency of the api package. Nothing else uses it: MCP clients bring their own model. To build a server image without it, pass --build-arg WITH_ASSISTANT=false to docker build. If FEATURE_ASSISTANT is on and the packages are missing, the server still starts, logs a warning, and each assistant turn fails with a message that names the missing packages.

ruleAggregates. Off because GROUP is not yet in SPARQL RL.

rulesAllowInvalidSave is off because saving a document that does not parse is a deliberate choice rather than a normal one. It is on in the rules-conformance recipe, because about a third of the W3C suite's documents are meant to be unparseable and are stored anyway.

The flags that default on grant no capability the deployment did not already have. A test runs a callable that is runnable from its own screen. A data graph is reference RDF you paste into your own library. A tuple set is tabular input you register in your own library, spliced into a VALUES clause at execution rather than loaded into a store. Argument sets have been reachable from the query screen since they existed; their flag exists so the rail section can be hidden without touching the query screen's Inputs tab, which is the same entity seen from its callable.

Two rules the codebase follows

A switched-off feature has no doors. A control that leads into a section this build does not draw is absent, not disabled. A greyed control reads as "broken" where the truth is "not a thing here", and a link into a section that is not drawn navigates nowhere. This is why the flag has to be consulted where the control is written, not only where the section is.

An input is not a feature. tupleSets, dataGraphs and argumentSets name entities that are consumed from screens which are not their own section: a query fills a VALUES clause from a tuple set, a rule set runs against a data graph, a callable is called with an argument set. Turning one of these off hides its rail section and the doors into it, and does not stop a callable that names one from resolving and running. This is why their routes stay registered: a saved query whose argument set is unreachable is a query that cannot run.

Two test files enforce these rules by inventory — each door is listed, with the condition that holds it, so a new door fails the suite until someone says which condition applies:

ruleAggregates: SRL GROUP aggregation

FEATURE_RULE_AGGREGATES, default off.

The flag decides whether the server parses GROUP, the aggregation element of w3c/data-shapes#1347. See the SRL reference. The flag exists because the pull request is not merged yet; once it is, GROUP becomes part of SRL and the flag goes. There is no per-version switch beside it, because a GROUP is entirely inside the rule text.

With the flag off, GROUP is a syntax error wherever the server parses a rule: saving a rule, the rule set analyzer and SRL preview, the SRL import, the rules playground and stratification. The message names the flag's parse option:

SRL syntax error: GROUP requires the rule-aggregates extension (parse with { aggregates: true })

A rule saved while the flag was on keeps its compiled program, and its rule set version keeps a stored stratification report, so a run can reach the rule without parsing it. The executor checks each rule for GROUP before running it, and with the flag off the run ends with status failed and the error:

RuleVersion <id> uses the rule-aggregates extension, which is not enabled on this server

The W3C conformance checks parse with GROUP off whatever the flag says, so a document using it is not reported as conformant while the pull request is open.

The SPA reads NUXT_PUBLIC_FEATURE_RULE_AGGREGATES but draws nothing different on it.

The browser build

packages/web is built ahead of time, so its flags are resolved at build time by packages/web/nuxt.config.ts. For each flag it reads NUXT_PUBLIC_FEATURE_<NAME> first and falls back to the plain FEATURE_<NAME>, then hands the result to the same buildFeatureFlags the API uses, so an unset variable lands on the same default in both halves.

NUXT_PUBLIC_FEATURE_ETL  ??  FEATURE_ETL  ??  the default (off)

The Justfile recipes show the pairing: run-local-rules-tests sets FEATURE_* for the API and run-frontend-rules sets the matching NUXT_PUBLIC_FEATURE_* for the SPA, because the two are separate processes and each reads its own environment. Setting them only on the API would leave the rail showing sections the API no longer serves.

Overriding a deployed static build

A built SPA can be re-flagged without rebuilding. The plugin packages/web/src/plugins/runtime-config.client.ts fetches /config.json at startup and merges what it finds over the build-time values:

{
  "apiBaseUrl": "https://sqlib.example.org",
  "featureFlags": { "etl": false, "assistant": false }
}

Keys are the flag keys, not the environment variable names, and a partial object is merged over the build-time flags rather than replacing them. The same file also overrides authIssuer, authClientId, authAudience, authScope, authTokenStorage and authRequireLogin. A missing file, a response that is not JSON, or a parse failure logs a warning and leaves the build-time values in place, so a deployment that serves no /config.json behaves exactly as it was built.

This changes the browser only. The server's own flags come from its environment, and a flag turned on in /config.json that the API has off produces a section whose requests answer 404.