Open KnowledgeOpen Knowledge
Guides

Configuration

Config schema, YAML hierarchy, environment variables, CLI flag overrides, and the in-app Settings pane.

Open Knowledge uses hierarchical YAML configuration stored in .ok/config.yml files. Both config files are optional -- sensible defaults cover everything.

There are three ways to edit config:

  • Settings pane in the editor (Cmd-, or App menu → Settings…). Schema-driven form, auto-save per field.
  • Your IDE, with $schema-driven autocomplete + validation (scaffolded by ok init).
  • AI agents via the set_config / get_config / set_folder_rule MCP tools.

All three share a single Zod schema and a single validation core, so a write that lands one way reflects everywhere — Settings panes refresh live when an external editor or MCP tool changes the file.

Config hierarchy

Each layer overrides the previous, from left to right. The merge is a deep merge: leaf values override, but arrays replace (not concatenate). There is no walk-up-tree discovery -- the loader checks exactly two fixed paths:

  1. User config -- ~/.ok/config.yml
  2. Workspace config -- ./.ok/config.yml (relative to the working directory)

Every schema section uses Zod's looseObject mode: unknown keys are preserved on disk through round-trip writes rather than rejected at load time. This makes upgrades forgiving -- an existing file with a removed field (e.g., a leftover sync.pushIntervalSeconds: 30) loads cleanly, the engine ignores it, and ok config migrate can drop it later. Schema-shaped fields still validate strictly; loose-mode only relaxes the "no extra keys" check.

Schema reference

FieldTypeDefaultDescription
content.dirstring"."Content directory relative to project root. Exclusions live in .okignore (root + nested) — see Content filtering.
server.hoststring"localhost"Server bind address
server.openOnAgentEditbooleanfalseWhen true, ok start opens the ok ui URL in the default browser on the first agent write
mcp.autoStartbooleantrueWhen true, ok mcp detach-spawns ok start as a sibling process if no live server.lock is found. Set false to force disk-only mode. The env var OK_MCP_AUTOSTART=0 wins over this setting.
mcp.tools.search.maxResultsnumber50Max rows returned by the MCP search tool.
mcp.tools.read_document.historyDepthnumber5Number of shadow-repo history entries surfaced by the MCP read_document tool.
preview.baseUrlstring?(unset)Override for the previewUrl emitted by MCP tools. Workspace-only — illegal at user scope. Takes effect when ui.lock is absent; the env var OPEN_KNOWLEDGE_PREVIEW_BASE_URL wins over this setting.
folders[]FolderRule[][]Per-folder default frontmatter. Each entry is {match: glob, frontmatter: {title?, description?, tags?}}. Editable in the Settings pane's Folders section, per-rule via the set_folder_rule MCP tool, or as a whole array via set_config({patch: {folders: [...]}}). Order matters: later rules override earlier scalars; tags concatenate and dedupe across all matching rules.
appearance.theme"light" | "dark" | "system"?(unset)Editor theme. User-scope by default. When unset, the chrome FOUC script falls back to localStorage + system preference; the first explicit Settings-pane write canonicalizes the value into config.yml.
appearance.editorModeDefault"wysiwyg" | "source"?(unset)Initial editor mode for newly opened documents. Same dual-track behavior as appearance.theme.
github.oauthAppClientIdstring"Ov23li..."OAuth App client ID for Device Flow. Override for forks or custom OAuth Apps.

Built-in directory skips

The following directories are always excluded from traversal regardless of .gitignore or .okignore rules. They are never user-authored content, and some (notably node_modules with pnpm) contain broken symlinks that would crash the file walker.

CategoryDirectories
Package managers / runtimesnode_modules, .venv, venv, env, __pycache__, vendor
Build outputdist, build, out, output, .next, .nuxt, .svelte-kit, .astro
Tool caches.turbo, .cache, .parcel-cache, coverage
VCS / project state.git, .ok

These skips apply when the directory name appears as the first path segment. For example, dist/ at the content root is skipped, and paths like dist/foo/bar are also skipped because dist is the first segment. Nested instances under non-skipped paths (e.g. docs/dist/) rely on .gitignore or .okignore patterns.

Example configs

Apply a default editor theme + MCP search cap to all projects on this machine:

~/.ok/config.yml
appearance:
  theme: dark
mcp:
  tools:
    search:
      maxResults: 100

Point at a subdirectory:

./.ok/config.yml
content:
  dir: docs

Exclude drafts via .okignore:

.okignore
drafts/
archive/

Uploads

There is no upload.* user-facing config. Attachment placement (co-located with the referencing doc), emit shape (![[file.ext]] for supported extensions, standard markdown link for opaque types), same-directory sha256 dedup with a toast notice, and the wiki-embed extension list are all fixed defaults. Uploads stream to disk end-to-end with on-the-fly sha256, so there is no user-facing size cap — drop a 500 MB video and it works. The only rejection axis is disk fullness, which surfaces as HTTP 507 (storage-full). Legacy configs that still carry upload.* keys parse cleanly (the section is silently stripped). See Assets and embeds for the full picture.

Sync configuration

Projects with a git remote auto-enable sync. The sync engine ships opinionated defaults (pull every 30s, push every 60s, auto-commit + auto-push + auto-pull on); these are no longer YAML-tunable -- the prior sync.* schema fields were dropped to keep the engine's lifecycle in one place. Loose-mode preserves any leftover sync.* keys on disk, but the engine ignores them; run ok config migrate to clean them up.

See GitHub Sync for the engine's full behavior.

Environment variables

VariableOverrides
PORTBind port for ok start (and ok ui). Per-machine only -- there is no schema field; use this env var or the --port CLI flag.
HOSTserver.host
OK_MCP_AUTOSTARTSet to 0 to disable ok mcp's auto-spawn of ok start. Overrides mcp.autoStart in config.
OPEN_KNOWLEDGE_PREVIEW_BASE_URLOverrides the previewUrl base emitted by every MCP tool. Wins over ui.lock discovery and preview.baseUrl config. Use for tunnels, CI, or cloud-deployed previews.
OPEN_KNOWLEDGE_GITHUB_CLIENT_IDgithub.oauthAppClientId — override the OAuth App client ID used for Device Flow

Precedence: CLI flags > environment variables > project config > user config > Zod defaults. A pre-existing live server.lock always takes precedence over OK_MCP_AUTOSTART=0 — the env var only suppresses the spawn path, it doesn't prevent MCP from connecting to a server the user started manually.

CLI flags

--port and --host are the highest-precedence overrides. They take effect regardless of what YAML files or environment variables specify.

npx @inkeep/open-knowledge start --port 8080

Settings pane

Open the Settings pane from anywhere in the editor:

  • Cmd-, (or Ctrl-, on Linux/Windows builds)
  • App menu → Settings… (Electron desktop)
  • HelpPopover → Settings…
  • Command Palette → "Settings"

The pane has two sub-tabs:

  • This project — project-scope fields, persisted to ./.ok/config.yml.
  • User — user-scope fields, persisted to ~/.ok/config.yml.

Either-scope fields appear in both tabs with a "modified at this scope" indicator showing where the current value lives. Edits auto-save per field — text inputs commit on blur or Enter; toggles and selects commit on change. Hover any modified field for a "Reset to default" affordance.

Editing is real-time: the pane is bound to two Hocuspocus Y.Text-only synthetic docs (__config__/project and __user__/config.yml) over the same WebSocket as the editor. External edits — from your IDE, an MCP tool, or another ok start instance — propagate via a chokidar file watcher into Y.Text and the open Settings pane refreshes within ~500ms. No reload needed.

Validation runs in three places (defense-in-depth):

  1. Client-side walker rejects invalid intermediate values before they leave the browser.
  2. Headless writers (MCP / CLI / seed) run the same Zod parse before any disk write.
  3. Persistence hook parses + validates Y.Text on every commit; if a malformed mutation slips past 1+2 (a buggy client, a hand-edit during the transaction window), the server reverts Y.Text to last-known-good and broadcasts a config-validation-rejected notification — the pane shows a toast and flashes the offending field.

Settings is unavailable in the Electron Navigator window — it has no project context to scope config to.

Folders section

folders[] rules render as a dedicated section in the Settings pane (between Preview and MCP in the section list). Each row exposes the four editable keys — Match (the glob pattern), Title, Description, and Tags — alongside per-row controls:

  • Add folder rule appends an empty {match: '', frontmatter: {}} entry and focuses the new Match input. The row stays in error state (Zod requires match to be a non-empty glob) until you type a valid pattern; nothing lands on disk before that.
  • Move up / Move down reorder rules. Rule order is load-bearing — later rules override earlier scalars when their globs both match a doc, so reordering is a real edit, not a cosmetic one. Each move commits.
  • Remove (trash icon) drops the row and commits the new array.

Tags uses a pill input: type a tag and press Enter, comma, or Tab to commit it as a Badge pill. Backspace on an empty input removes the last pill. Duplicates are silently deduped, and any uncommitted draft is auto-committed when the field loses focus. Click the × on any pill to remove it individually.

Like scalar fields, per-row edits commit on blur or Enter — but the wire write is always the whole folders[] array, mirroring the all-or-nothing transactional semantics of the set_folder_rule MCP tool and applyFolderRulesUpsert core helper. Validation runs against the merged config; if any rule fails (e.g. an empty match slips through, or a sibling row is invalid for an unrelated reason), no rules land on disk and the offending Match field shows an inline validation error. Rejections from external writers (CLI, MCP, hand-edits to disk) also briefly flash the affected row in addition to showing the inline error.

IDE intellisense

ok init scaffolds a magic comment on line 1 of generated .ok/config.yml:

.ok/config.yml
# yaml-language-server: $schema=https://unpkg.com/@inkeep/open-knowledge@0.2/dist/config-schema.json
# Open Knowledge — project configuration
content:
  dir: .

Any LSP-aware editor (VS Code, JetBrains, Helix, Zed, vim with yaml-language-server) picks this up and provides autocomplete, hover docs, and inline validation against the published JSON Schema. The URL is pinned to the installed CLI's MAJOR.MINOR version so your autocomplete stays locked to the schema you wrote against; re-run ok init after upgrading to bump the pin.

The schema artifact is published to npm at @inkeep/open-knowledge/dist/config-schema.json, generated from the same Zod source the runtime validates with. A SchemaStore catalog entry (with a fileMatch of **/.ok/config.{yml,yaml}) is planned so editors that consult SchemaStore — most JetBrains IDEs by default — will auto-detect without needing the magic comment. Until that lands, the magic comment on line 1 is the primary intellisense mechanism.

For CI / pre-commit hooks, ok config validate loads the merged config through the same Zod gate and exits non-zero with a file:line:col snippet on failure.

Verifying config changes

After editing config, run preview to verify the resolved content scope:

npx @inkeep/open-knowledge preview

This prints the file count and a sample of matched paths -- useful for confirming that .okignore changes had the intended effect.