| title | Configuration Reference |
|---|
structured-context uses the first config file it finds, in this order:
--config <path>$SCTX_CONFIG- The nearest
.sctx.jsonor.sctx/config.json, looking in the working directory and then each parent folder ~/.config/structured-context/config.json(or$XDG_CONFIG_HOME/structured-context/config.json)
It is recommended to keep a local config at the base of the notes it describes (for example the root of an Obsidian vault), or to use the global config. A local config is either .sctx.json or config.json inside a .sctx/ folder. A folder can't hold both. The CLI and agent sessions look in parent folders only, so run commands from inside the folder that holds the config. To use the spaces from another config file too, list it in includeSpacesFrom.
The config root is the folder that holds .sctx.json or .sctx/, or the global config's folder. Relative paths in a config file resolve from its root. In a vault-root config, "path": "." is the whole vault and "path": "notes" is one folder, whichever of the two file names it uses.
Files that structured-context reads besides the config go in the root's .sctx/ folder (for the global config, its own folder), one subfolder each:
| Folder | Holds |
|---|---|
.sctx/plugins/ |
Local plugins |
.sctx/schemas/ |
Custom schemas, found by file name ("schema": "my_wiki.json") |
.sctx/map-views/ |
Map view definitions, read by structured-context-map-views |
sctx spaces shows which config file is used and reports problems with it. sctx spaces --json returns the same information for tools and hooks.
sctx spaces add wiki notes --schema knowledge_wiki.jsonThis adds the space to the config file found from the working directory, or to the one named with --config. When no .sctx.json is found, or only the global config exists, pass --config <folder>/.sctx.json (creates the file) or --global. If the config file has comments or trailing commas, the command prints the entry to add by hand.
Custom schemas can sit anywhere. A schema in .sctx/schemas/ is found by its file name, before one of the same name in the root; reference others by relative path, for example ./schemas/my_wiki.json.
See config.example.json for the full structure.
A space is a named directory or single file registered in the config. Example:
{
"spaces": [
{
"name": "ProductX",
"path": "/path/to/space",
"schema": "strategy_general.json"
}
]
}schema — the schema used to validate nodes in the space. Can be:
- A bundled schema name (e.g.
strategy_general.json,strict_ost.json) - A local path relative to the config file (e.g.
./schemas/custom.json) or absolute path - A plugin schema prefixed with the plugin name (e.g.
sctx-wardley-mapping/wardley_map.jsonorwardley-mapping/wardley_map.json). Specifying a plugin-prefixed schema automatically loads that plugin for the space. - A simple filename (e.g.
wardley_map.json) provided by a plugin explicitly declared underplugins.
includeSpacesFrom — import space definitions from other config files, for example a shared team config or your global config ("~/.config/structured-context/config.json"; a leading ~ is expanded). A space defined in the including file overrides an included space of the same name; between includes, later entries win. Only spaces are taken from an included file.
A config must define at least one of spaces or includeSpacesFrom. When using includeSpacesFrom alone, spaces can be omitted entirely.
plugins maps a plugin name to its config. Declare it at the top level for every space, or on a space to override the top level:
{
"spaces": [
{
"name": "ProductX",
"path": "/path/to/space",
"plugins": {
"markdown": { "fieldMap": { "record_type": "type" } }
}
}
],
"plugins": {
"sctx-example": { "baseUrl": "https://example.com" }
}
}Plugin names start with sctx-, which config may leave out. markdown is the built-in markdown plugin. A named plugin is found in the root's plugins folder (.sctx/plugins/<name>), else as an installed package. Its config is checked against the plugin's schema, and relative paths in it resolve from the config root.
Where a plugin comes from decides when it is used:
| Plugin | Parse and template sync | Render formats |
|---|---|---|
| Built-in | Every space | Every space |
In a plugins folder (.sctx/plugins/sctx-*) |
Spaces that name it in plugins |
Every space |
Installed package (sctx-*) |
Spaces that name it in plugins |
Spaces that name it in plugins |
structured-context evaluates semantic rules with a model provider: a named definition that says which wire API to speak, which model to use and where. A provider definition combines an api, explicit values or environment-variable names, and the key source.
Bundled providers work without any configuration: a fixed model per vendor chosen by this project, plus -env variants that also read the vendor's usual model and endpoint variables (OPENAI_MODEL, OPENAI_BASE_URL, ANTHROPIC_MODEL), falling back to the bundle's defaults where a variable is unset. Bundles without -env read no environment beyond the key: they behave the same on every machine.
Run sctx providers for the authoritative list of bundled and configured providers with their effective API, endpoint, model and key variable. Keys are never shown. bun run test:models checks that each bundled model resolves on its endpoint.
A provider's api names the request shape, potentially supported by multiple vendors.
| API | Shape | Bundled providers |
|---|---|---|
openrouter-decisions |
Decisions API, closed-set choice questions, answers with confidence and probabilities |
openrouter-jev, openrouter-openai-decisions |
typesafe-systemone |
Same decisions shape (currently) | typesafe-jev |
openai-decisions |
OpenAI's Decisions API: same concept, its own request/response shape | openai-decisions |
anthropic-messages |
Chat: structured JSON verdict | anthropic, anthropic-haiku-5-5, anthropic-env |
openai-completions |
Chat: OpenAI-compatible; works with Ollama, vLLM and OpenRouter | openai, openai-env, openrouter-openai |
Bundled provider names follow the pattern:
<vendor>— this project's recommended model for that vendor; can change between releases.<vendor>-<model>(e.g.anthropic-haiku-5-5) — pins a model id, so it does not drift when the<vendor>bundle's recommendation moves.<vendor>-env— hydrates model, endpoint etc from the vendor's usual environment variables.openrouter-<vendor>-<purpose>(e.g.openrouter-openai-decisions) — aggregating vendor's endpoint variant
Configured providers live in modelProviders at the top level, and replace a bundled provider of the same name. Fields a configured provider leaves out still take the API defaults:
{
"modelProviders": {
"mine": { "api": "openrouter-decisions", "apiKeyEnv": "MY_JEV_KEY" },
"local": { "api": "openai-completions", "endpoint": "http://localhost:11434/v1" }
}
}| Field | Description |
|---|---|
api |
Required. The wire API (see the table above and Choosing a model provider), or fake (for tests). |
model |
A fixed model. Its default depends on the API. |
modelEnv |
Environment variable holding a model override. An explicit model wins; a variable set but empty is an error. |
endpoint |
A fixed API URL. |
endpointEnv |
Environment variable holding an endpoint override. Same precedence as modelEnv. |
apiKeyEnv |
The environment variable that holds the key. Defaults to the API's usual variable (see the table above). A local openai-completions endpoint can need no key. |
apiKey |
The key itself. Use apiKeyEnv instead, so that keys stay out of config files. |
Resolution per field: the explicit value wins, then the named environment variable, then the API default. Provider implementations read no environment; all hydration happens before one runs.
The environment holds only keys and the variables a -env bundle names. No environment variable selects a provider.
The top-level semantic.provider names the model provider that evaluates semantic rules: a bundled provider or an entry in modelProviders. A space can set its own semantic, which replaces the top-level one for that space, for example to keep a private space on a local model:
{
"semantic": { "provider": "openrouter-jev" },
"spaces": [
{ "name": "private-notes", "path": "/path/to/notes", "semantic": { "provider": "local" } }
]
}Config does not turn semantic evaluation on. Each run asks for it with --semantic. --semantic <provider> also selects a different provider for that run.
Set under plugins.markdown per space.
Maps file/frontmatter field names to canonical schema field names:
{ "fieldMap": { "record_type": "type" } }templateDir— directory containing template files (used bytemplate-syncand excluded when parsing)templatePrefix— filename prefix for templates (default blank)
Names the pages that stand for their folder (see Folder index). A page whose basename matches, case-insensitively, takes the containing folder's name as its title (eg Project A/index.md is parsed to Project A). An explicit title: still wins. A match at the space root keeps its basename. When empty or absent, titles come from the basename.
{ "plugins": { "markdown": { "folderIndex": ["Overview", "index"] } } }Automatically assigns a node type based on folder structure when no type field is present in frontmatter. Explicit type: always takes precedence.
mode — controls the matching strategy:
"folder-name"(default) — matches the leaf directory name case-insensitively against schema type names and alias keys"off"— disables inference entirely
{
"plugins": {
"markdown": {
"typeInference": { "mode": "folder-name" }
}
}
}folderMap — explicit map from folder path (relative to space root) to type name or alias. When set, replaces auto-matching entirely; only folders listed in the map are inferred.
{
"plugins": {
"markdown": {
"typeInference": {
"folderMap": {
"Research": "source",
"Personal": "note",
"topics/concepts": "concept"
}
}
}
}
}Longest-prefix matching is used when keys overlap (e.g. a/b and a/b/c both present). Trailing slashes in keys are normalised. Values may be type aliases (resolved to canonical type). An unresolvable value throws a hard error at parse time.
Named filter expressions can be defined per space under views. Each view has an expression field:
{
"spaces": [
{
"name": "my-space",
"path": "/path/to/space",
"views": {
"active-solutions": {
"expression": "WHERE resolvedType='solution' and status='active'"
},
"solutions-under-active-opportunity": {
"expression": "WHERE resolvedType='solution' and $exists(ancestors[resolvedType='opportunity' and status='active'])"
}
}
}
]
}Use a view name with sctx show <space> --filter <view-name>.
See sctx docs concepts for full filter expression syntax.
The tool executes JSONata expressions defined in schema files for rule validation. A maliciously crafted schema could make JSONata access JavaScript's prototype chain and execute arbitrary code. Only use schemas you've created or reviewed personally.