# Folders and templates

> Folder properties in `.ok/frontmatter.yml`, leaf-to-root template resolution, and MCP `write({ document: { template } })` instantiation semantics.

- Repository: sashimikun/open-knowledge
- GitHub: https://github.com/sashimikun/open-knowledge
- Human docs: https://grok-wiki.com/public/docs/sashimikun-open-knowledge-5c45105c876e
- Complete Markdown: https://grok-wiki.com/public/docs/sashimikun-open-knowledge-5c45105c876e/llms-full.txt

## Source Files

- `docs/content/advanced/folders-and-templates.mdx`
- `packages/core/src/templates/template-format.ts`
- `packages/server/src/mcp/tools/write.ts`
- `packages/server/src/mcp/tools/verb-schemas.ts`
- `packages/core/src/frontmatter/tags.ts`
- `docs/content/features/ignore-patterns.mdx`

---

---
title: "Folders and templates"
description: "Folder properties in `.ok/frontmatter.yml`, leaf-to-root template resolution, and MCP `write({ document: { template } })` instantiation semantics."
---

Open Knowledge stores folder metadata in `<folder>/.ok/frontmatter.yml` and reusable document starters in `<folder>/.ok/templates/<name>.md`. Template resolution walks from the document's parent folder up to the content root; the nearest definition wins on filename collisions. Agents instantiate templates through MCP `write({ document: { path, template } })`, which strips the reserved `template:` identity block, applies `{{date}}` and `{{user}}` substitution, and writes via the collaboration server.

## Folder properties

Folder properties describe the folder itself. They do not inherit into child folders and do not flow into documents inside the folder.

| Aspect | Behavior |
| --- | --- |
| On-disk path | `<content-root>/<folder>/.ok/frontmatter.yml` (empty `folder` = content root) |
| Schema | Open-shape YAML — any top-level keys |
| Conventional keys | `title`, `description`, `tags` (surfaced in navigation, search, folder overview, and `exec` listings) |
| Inheritance | None — each folder reads only its own file |
| Patch semantics | Merge-patch: set a key to update it, set a key to `null` to delete it, omit keys to leave them unchanged |
| Empty result | Clearing all keys deletes `frontmatter.yml` and removes an empty `.ok/` directory |

A folder gets a `.ok/` directory when you add properties or its first template. Folders with no special configuration stay plain directories.

<ParamField body="path" type="string" required>
Content-root-relative folder path. Example: `meetings` or `research/sources`.
</ParamField>

<ParamField body="frontmatter" type="object">
Open-shape property map. Conventional keys: `title`, `description`, `tags`.
</ParamField>

<RequestExample>

```json
write({
  folder: {
    path: "meetings",
    frontmatter: {
      title: "Meetings",
      description: "Standups, planning sessions, and retrospectives",
      tags: ["meetings"]
    }
  }
})
```

</RequestExample>

<ResponseExample>

```text
Created folder meetings.
Set folder properties (meetings/.ok/frontmatter.yml).
```

</ResponseExample>

<Note>
Use `write({ folder })` to create a new folder. To change an existing folder's properties, use `edit({ folder: { path, frontmatter } })`. A `409` on create means the folder already exists.
</Note>

### Read folder metadata

The editor folder overview and MCP `exec` listings expose a folder's own `title`, `description`, and `tags` when present. `GET /api/folder-config?path=<folder>` returns the same metadata plus `templates_available`.

```bash
exec("ls meetings/")
```

Enriched directory output includes `title`, `description`, `tags`, and `templates_available` when the folder has properties or templates.

## Template files

A template is a markdown file at `<folder>/.ok/templates/<name>.md`. The filename (without `.md`) is the template id agents pass to `write({ document: { template } })`.

| Constraint | Rule |
| --- | --- |
| Name pattern | `^[A-Za-z0-9_-]+$` — letters, digits, underscore, hyphen only |
| File extension | `.md` only (non-`.md` files in `templates/` are ignored) |
| Identity metadata | `title` (required at write time), optional `description` and `tags` |
| Starter content | Markdown body plus optional starting document properties |

### On-disk format

Templates use a single frontmatter block. The reserved `template:` key holds picker identity (name, description, tags shown in menus). Every other top-level key becomes starting properties on documents created from the template.

```md title="meetings/.ok/templates/standup.md"
---
template:
  title: Daily standup
  description: Standup notes scaffold
type: meeting-note
status: draft
tags: [standup]
---

# Standup — {{date}}

Recorded by {{user}}

## Yesterday

## Today

## Blockers
```

At instantiation, `parseTemplateFile` peels the `template:` block and returns `starterContent` — the remaining frontmatter keys plus the markdown body. Legacy templates with two stacked `---` blocks still parse; saving rewrites them to the single-block form.

<Warning>
Do not declare a top-level `template:` key inside the `content` field when creating a template via MCP. That key is reserved and rejected at write time with `TEMPLATE_RESERVED_KEY`.
</Warning>

### Create or update a template

<ParamField body="path" type="string" required>
Template path as `<folder>/<name>`. Example: `meetings/standup`.
</ParamField>

<ParamField body="content" type="string" required>
Starter markdown. A leading `---…---` block sets starting document properties; the markdown below is the body.
</ParamField>

<ParamField body="frontmatter.title" type="string" required>
Human-readable label shown in the New File dialog and agent template menus.
</ParamField>

<ParamField body="frontmatter.description" type="string">
Optional one-line summary beside the title in pickers.
</ParamField>

<ParamField body="frontmatter.tags" type="string[]">
Optional tags stored under `template:` on disk.
</ParamField>

<RequestExample>

```json
write({
  template: {
    path: "meetings/standup",
    content: "# Standup — {{date}}\n\nRecorded by {{user}}\n",
    frontmatter: {
      title: "Daily standup",
      description: "Standup notes scaffold"
    }
  }
})
```

</RequestExample>

Project-wide templates live at `.ok/templates/<name>.md` (empty folder segment = content root). Folder-scoped templates live under that folder's `.ok/templates/`.

## Leaf-to-root template resolution

When creating a document, Open Knowledge resolves templates against the document's **parent folder**, not the document path itself. For `posts/2026/launch`, resolution starts at `posts/2026/`.

```mermaid
flowchart TB
  subgraph resolve["resolveTemplatesAvailable(parentFolder)"]
    L["1. Collect local templates from parent folder"]
    W["2. Walk ancestors leaf → root as inherited"]
    R["3. Collect project-root templates as inherited"]
    C["4. Closest folder wins on name collision"]
  end
  Doc["write document path: posts/2026/launch"] --> Parent["parentFolder = posts/2026"]
  Parent --> L --> W --> R --> C
  C --> Menu["templates_available menu"]
```

| Scope | Source | Visibility |
| --- | --- | --- |
| `local` | Templates in the parent folder's `.ok/templates/` | Available to documents created in that folder and its descendants |
| `inherited` | Templates from an ancestor folder or the content root | Surfaced to descendant folders, marked `inherited` |

Resolution rules:

- **Closest wins.** If `meetings/` and `meetings/prep-notes/` both define `prep-notes`, a document under `meetings/prep-notes/` uses the local copy.
- **Siblings are invisible.** Templates in `research/` do not appear when creating a document in `meetings/`.
- **Descendants do not bubble up.** A template defined only in `meetings/prep-notes/` is not listed for `meetings/` itself.
- **Project root is inherited everywhere.** Templates at `.ok/templates/` apply under every folder unless overridden locally.
- **First match sticks.** Once a name is collected from the nearest folder, ancestor definitions with the same filename are skipped.

Each resolved entry carries `name`, `title`, `description`, `path`, `source_folder`, and `scope`. Agents discover the menu through `exec("ls <folder>/")`, which includes `templates_available`.

## MCP `write({ document: { template } })` instantiation

Template-based document creation is a server-routed write. The collaboration server must be running (`ok start`).

<ParamField body="path" type="string" required>
Document path without extension. Slashes encode the target folder; missing parents are created. Example: `posts/launch`.
</ParamField>

<ParamField body="template" type="string" required>
Template name (filename without `.md`). Resolved leaf-to-root against the parent folder of `path`. Mutually exclusive with `content`.
</ParamField>

<ParamField body="frontmatter" type="object">
Optional document properties applied **after** instantiation via a separate frontmatter patch.
</ParamField>

<ParamField body="position" type="string">
Ignored when `template` is set — instantiation always uses `replace`.
</ParamField>

### Instantiation pipeline

1. **Resolve parent folder** — `parentFolderOf(docName)` extracts the directory portion of `path`.
2. **Match template** — `resolveTemplatesAvailable` builds the menu; the first entry whose `name` equals `template` wins.
3. **Read and peel** — `instantiateDoc` strips the `template:` identity block; remaining frontmatter and body become the draft document.
4. **Substitute** — `{{date}}` → UTC `YYYY-MM-DD`; `{{user}}` → connected agent display name (empty string when unknown).
5. **Write** — `POST /api/agent-write-md` with `position: "replace"`.
6. **Optional frontmatter patch** — If `frontmatter` is also supplied, `POST /api/frontmatter-patch` merges properties onto the new document.

<RequestExample>

```json
write({
  document: {
    path: "posts/launch",
    template: "blog-post"
  }
})
```

</RequestExample>

<ResponseExample>

```text
Written successfully (instantiated from template "blog-post").
```

</ResponseExample>

### Error cases

| Error | Cause |
| --- | --- |
| `either content or template must be provided` | Both fields omitted |
| `TEMPLATE_AND_CONTENT_BOTH_SET` | Both `template` and `content` supplied |
| `template "<name>" not found for folder "<parent>"` | Name not in the resolved menu; error lists available templates with scope |
| `failed to read template at <path>` | Template file missing or unreadable on disk |
| `document created from template but frontmatter failed` | Body written; subsequent frontmatter patch failed |
| Hocuspocus not running | Server required for document writes |

<Tip>
When choosing a template, agents should read `title` and `description` from `templates_available` — the `name` field is the id passed to `write`, but the title is the human-facing label.
</Tip>

### `template` versus `content`

| Input | Behavior |
| --- | --- |
| `content` | Literal markdown body; optional `frontmatter` composes inline YAML (forces `replace`) |
| `template` | Loads starter content from disk; identity metadata never copied to the document |
| Both | Hard error — use `template` then `edit` for post-create changes |

Batch writes via `write({ documents: [...] })` follow the same per-entry rules.

## Substitution tokens

Only two placeholders are allowed in template bodies:

| Token | Replacement |
| --- | --- |
| `{{date}}` | Today's date in UTC as `YYYY-MM-DD` |
| `{{user}}` | Display name of the person or agent creating the document |

Any other `{{...}}` token is rejected when saving a template (`TEMPLATE_UNKNOWN_VARIABLE`). Unknown tokens left in an on-disk file are left unchanged at instantiation (the allowlist replaces only recognized tokens).

Placeholders may appear in both the starter frontmatter and the markdown body. A template with `title: {{date}}` produces a document whose `title` property is the substituted date.

## Storage layout

:::files
<content-root>/
├── .ok/
│   ├── frontmatter.yml          # content-root folder properties
│   └── templates/
│       └── daily-note.md        # project-wide template
├── meetings/
│   ├── .ok/
│   │   ├── frontmatter.yml      # meetings/ folder properties
│   │   └── templates/
│   │       └── standup.md       # folder-local template
│   └── 2026-01-15-standup.md    # document (own properties only)
└── research/
    └── sources.md
:::

Paths are relative to the **content root** configured by `content.dir` in `.ok/config.yml` (default `.`, the project root). The editor folder overview, Settings → Project templates, and MCP `write` / `edit` with `folder` or `template` targets all write to these locations.

<Info>
Folder properties and templates are not affected by `.okignore`. Ignore patterns hide content from search and agent context but do not remove `.ok/` metadata directories from the filesystem.
</Info>

## Related pages

<CardGroup>
  <Card title="Project scaffold" href="/project-scaffold">
    `.ok/` directory layout, config scopes, and `content.dir` semantics.
  </Card>
  <Card title="Editor workflows" href="/editor-workflows">
    Folder overview UI, Properties panel, and the New File template picker.
  </Card>
  <Card title="MCP tools reference" href="/mcp-tools-reference">
    Full `write` and `edit` schemas for `document`, `folder`, and `template` targets.
  </Card>
  <Card title="Configuration reference" href="/configuration-reference">
    `content.dir` and published config schema paths.
  </Card>
</CardGroup>
