docs/WORKSPACE.md
This document describes how a SiYuan workspace is organized on disk. It complements
SY-FORMAT.md: the latter covers the internal JSON structure of a.syfile, while this one covers the overall file-system layout of the workspace. All conclusions are verified against a real workspace and the kernel source.
A SiYuan workspace is a self-describing directory tree: notebooks, documents, and assets all live on disk as readable files and directories. The filename is the ID; the directory structure is the document hierarchy. There is no binary database recording "where documents are" — the file system itself is the single source of truth. The kernel reconstructs all notebooks and documents simply by walking the directories.
<workspace root>/ e.g. F:\SiYuan\
├── conf/
│ ├── conf.json # ★Workspace-level config (appearance/system/sync/editor...)
│ ├── ca.crt / cert.pem / key.pem # TLS certificates
│ └── windowState.json
├── data/ # DataDir — root of all notebook data
│ ├── .siyuan/ # Workspace-level hidden config (*ignore rules, etc.)
│ ├── assets/ # ★Global assets (images/audio/video/files)
│ ├── templates/ # Global templates (.md)
│ ├── widgets/ # Widgets
│ ├── plugins/ # Plugins
│ ├── emojis/ # Custom emoji
│ ├── snippets/ # Code snippets (CSS/JS)
│ ├── public/ # Static resources
│ ├── storage/ # Runtime indexes / cache / databases
│ └── <boxID>/ # ★Notebook (dir name = notebook ID)
│ ├── .siyuan/
│ │ ├── conf.json # ★Notebook config (BoxConf: name/icon/closed...)
│ │ └── sort.json # Custom sort mapping {docID: order}
│ ├── <docID>.sy # Document (JSON tree; filename = doc rootID)
│ └── <docID>/ # ★Children of that document (same-named pair)
│ ├── <childID>.sy
│ └── <childID>/ ... # Nesting of arbitrary depth
├── repo/ # Data repo (snapshots/sync)
├── history/ # Edit-history archive
└── temp/ # Temp/export files
The workspace root is chosen by the user (e.g. F:\SiYuan\). Whether a directory is a valid workspace is decided by: conf/conf.json exists and contains a kernelVersion field.
Fixed sub-directories under the root:
| Sub-dir | Purpose |
|---|---|
conf/ | Workspace-level config + TLS certificates |
data/ | All notebook data (the core of this document) |
repo/ | Data-snapshot repo (used for sync/history) |
history/ | Edit-history archive |
temp/ | Temp/export files |
corrupted/ | Quarantine for corrupted data (auto-moved by the kernel) |
data/The first level of data/ mixes two kinds of entries:
.siyuan/, assets/, templates/, widgets/, plugins/, emojis/, snippets/, public/, storage/.IsReservedFilename:
func IsReservedFilename(baseName string) bool {
return "assets" == baseName || "templates" == baseName || "widgets" == baseName ||
"emojis" == baseName || ".siyuan" == baseName || strings.HasPrefix(baseName, ".")
}
That is, assets / templates / widgets / emojis / .siyuan, as well as anything starting with ., cannot be used as a notebook or document name.
ListNotebooks enumerates data/ and, for each directory:
ast.IsNodeIDPattern);box.ID (notebook ID).This is the single most important convention in the whole layout:
Document
A's on-disk representation = theA.syfile + theA/directory (the latter exists only if A has children). All of A's child documents live insideA/; if a child has its own children, nest anA/B/directory — to arbitrary depth.
Real example (from find output):
20221126104620-m06prws/ # parent document A's directory
20221126104620-m06prws.sy # parent document A itself
20221126104620-m06prws/20230928134805-z11t56h.sy # A's child B
20221126104620-m06prws/20230928134805-z11t56h/ # B's child directory (grandchild layer)
20221126104620-m06prws/20230928134805-z11t56h/20240304105333-v7g5j1s.sy
Code evidence:
Ls): given a .sy path, strip the .sy suffix, check whether the same-named directory exists, and list its entries if so.GetChildDocDepth): walks the same-named directory accordingly.⚠️ This means: moving or renaming a document that has children must also move its same-named directory, otherwise the children get orphaned.
<boxID>/.siyuan/conf.jsonEach notebook directory contains a .siyuan/ hidden folder with:
| File | Purpose |
|---|---|
conf.json | Notebook config (BoxConf) |
sort.json | Custom sort mapping {docID: order} |
The BoxConf struct:
| Field | Type | Meaning |
|---|---|---|
name | string | Notebook display name |
sort | int | Sort weight |
icon | string | Icon (emoji hex code, e.g. "1f3af"; or a custom-icon filename) |
closed | bool | Whether the notebook is closed |
refCreateSaveBox | string | Target notebook for docs created via block-ref |
refCreateSavePath | string | Target path for docs created via block-ref |
docCreateSaveBox | string | Target notebook for new docs |
docCreateSavePath | string | Target path for new docs |
dailyNoteSavePath | string | Storage path for new daily notes (supports templates) |
dailyNoteTemplatePath | string | Template path for new daily notes |
sortMode | int | Sort mode |
⚠️ The notebook ID is NOT in
conf.json— the ID is the notebook directory name itself (see §3).
Read/write sites: GetConf / SaveConf. The path is hard-coded as <DataDir>/<boxID>/.siyuan/conf.json.
<workspace>/conf/conf.jsonBe careful to distinguish the two conf.json files:
| File | Location | Scope |
|---|---|---|
conf/conf.json | Workspace root | Workspace-level global config (appearance / langs / system / editor / sync / repo, etc.) |
<boxID>/.siyuan/conf.json | Inside a notebook | That notebook only (BoxConf) |
In practice conf/conf.json is ~42 KB and holds UI appearance, account, sync, AI, flashcard, and all other workspace-level settings.
data/.siyuan/data/.siyuan/ is different from the per-notebook .siyuan/ — the former holds workspace-level rules and state:
| File | Purpose |
|---|---|
syncignore | Sync-ignore rules (gitignore style) |
searchignore | Search-ignore rules |
embeddingignore | AI-embedding ignore rules |
indexignore | Indexing-ignore rules |
refsearchignore | Ref-search ignore rules |
publishAccess.json | Publishing access control |
filesys_status_check/ | File-system consistency-check state |
There is no
conf.jsonhere — the workspace config lives at the workspace root'sconf/conf.json(see §6).
Two coexisting locations:
data/assets/ — the main one. Naming convention looks like <orig-name>-<docID-suffix>.<ext> (e.g. 640-20240927104411-0jh7x96.webp). Referenced inside documents as the relative path assets/xxx.<notebook>/assets/ — mostly used by the built-in guide notebook; ordinary user notebooks generally don't have one.Asset-link prefix recognition: only assets/, emojis/, plugins/, public/, and widgets/ are accepted as legal asset-link prefixes.
YYYYMMDDHHMMSS-xxxxxx (14-digit timestamp + - + 6 random chars).YYYYMMDDHHMMSS-xxxxxxx (14-digit timestamp + - + 7 random chars).TimeFromID).[a-z0-9]..sy filename (without .sy) = the document rootID.filename ID == root.ID and auto-corrects mismatches.A document's absolute path on disk = data/<boxID>/<relative-path>, where <relative-path> looks like /20221126104620-m06prws/20230928134805-z11t56h.sy (POSIX style, leading /).
Extracting the doc ID from a path (GetTreeID): simply filepath.Base(path) with the .sy suffix removed.
HPath is not stored — it is computed on the fly when loading a document (LoadTreeByData):
/, drop the leading empty segment and the trailing self segment, yielding the ID segments of each ancestor document.<ancestorID>.sy and read its title via DocIAL() (a streaming read of Properties only, without parsing the whole tree)./ and append the current document's title → an HPath like /Parent/Child/Current..sy is missing, the kernel auto-creates an Untitled parent (issue #7376).This is the fundamental reason "directory structure is data": moving or renaming a file/directory directly changes the document's HPath and breadcrumbs — because HPath is entirely derived from the directory chain.
Consistent with SY-FORMAT.md §0.5: prefer HTTP API / MCP / CLI for mutating data, so the kernel handles index synchronization. Direct file-system operations are only for bulk offline migration, cold init, and similar scenarios.
When writing files directly:
.sy filename equals its rootID (§9).<boxID>/.siyuan/conf.json, not in .sy (§5).| Document | Layer | Question answered |
|---|---|---|
| This one (WORKSPACE) | File-system layer | "What does a workspace look like on disk? How are notebooks/documents/assets organized?" |
| SY-FORMAT | AST layer | "What does the JSON tree inside a .sy file look like? What node types/fields exist?" |
The two are complementary: this document tells you where a .sy file lives, what it's named, and who shares its directory; SY-FORMAT tells you what's inside it.