The app data folder
Where Plicine keeps everything it generates: caches, compiled blocks, trust decisions, the shared library and block checkouts. Nothing here is part of any vault, and the vault only ever receives one file back (blocks.lock).
Location
| Platform | Default |
|---|---|
| macOS | ~/Library/Application Support/plicine |
| Windows | %APPDATA%\plicine (or <home>\AppData\Roaming\plicine if APPDATA isn't set) |
Setting PLICINE_DATA_DIR overrides both. It's how a CI run gets a disposable data folder of its own.
Layout
| Path | Contents |
|---|---|
trust.json |
Every approve/deny decision made on this machine, keyed by block content hash |
agents.json |
Optional, written by hand: agents for the palette besides the built-in ones. See below |
agent-settings.json |
The settings last picked for each agent in the palette, such as its model, so a new conversation starts where the last one left off |
agent-palette.json |
Whether the note pointing out the agent palette has been dismissed, so it shows once rather than on every launch |
agent-cwd/ |
An empty folder every agent process starts in. The vault reaches the agent as its session's folder instead, so nothing a vault holds can decide which program npx runs |
library/ |
.block and .theme files installed with plicine install (without --community), available to every vault |
community/ |
.block and .theme files installed with plicine install --community |
packages/<hash>/ |
One extracted, compiled block per content hash: the package's own files at the root, plus dist/block.js and, if the block emits CSS, dist/block.css |
deps/<hash>/ |
One node_modules install per exact (package.json, bun.lock) pair a block declared, symlinked into that block's packages/<hash>/node_modules |
inline/<hash>/ |
One compiled module per distinct ts-block body, keyed by sha256("ts-block\0" + code) |
chrome/<version>/ |
The chrome-headless-shell downloaded for PDF export, one folder per pinned release, holding the archive's own chrome-headless-shell-<platform>/ folder. Written by plicine chrome download or by the first export on a machine with no browser |
vaults/<vault-hash>/pdf/<key>.pdf and .json |
Cached PDF renders, keyed by a hash of the document, theme, page size, block trust and hashes, and a renderer version; the .json file alongside holds { pages, warnings } |
vaults/<vault-hash>/storage/<block-id-hash>.json |
A flat JSON object of key/value pairs for one block's globalThis.plicine.storage, one file per block id, shared by every document in that vault |
vaults/<vault-hash>/sidebar.json |
The folders open in that vault's sidebar, as { "openFolders": [...] } with vault-relative paths, and the widths the sidebar and the block panel were last dragged to. It lives here rather than in the page's localStorage because the desktop app's server takes a new port every launch, which gives the page a new origin and empty storage |
vaults/<vault-hash>/index/documents.json |
The vault's search index: one entry per document, holding its title, headings, text, block names and props, and the documents it links to, each with the source line it came from. Rebuilt on start for whatever has changed and updated a document at a time from the folder watcher. See Search and backlinks below |
dev/<vault-hash>/<alias>/ |
A checkout made by plicine block checkout/edit: the block's own files, plus a .base/ snapshot and .plicine-dev.json sidecar plicine block push/status/diff use to find the vault and detect drift. See Editing an installed block |
A download that doesn't finish never looks like an install: the archive unpacks into a .download-* folder under chrome/ and is renamed into chrome/<version>/ only once the executable is there, so an interrupted download leaves nothing for the next run to trip over. An archive whose SHA-256 isn't the one pinned for the platform is thrown away the same way, before any of it is unpacked.
<vault-hash> is the first 16 characters of the SHA-256 of the vault's absolute, resolved path, so the same vault always maps to the same cache folder regardless of which command opened it, and two differently-located vaults never collide even if their contents are identical. <block-id-hash> is the first 16 characters of the SHA-256 of the block's id (its permanent @scope/name, not its content hash), so a block's stored data survives the block being updated to a new version and a new content hash.
A real data folder after installing and approving the two reference blocks and exporting one PDF looks like this:
plicine/
trust.json
library/
quote-table.block
packages/
223ba97aa0b7.../ (full 64-character hash)
manifest.json BLOCK.md package.json src/...
dist/block.js
229eec57928d.../
manifest.json BLOCK.md package.json src/...
dist/block.js
vaults/
20c6593f1ded5d4d/
index/
documents.json
pdf/
67cf17d7edf6....json
67cf17d7edf6....pdf
sidebar.json
trust.json records each decision by the block's full content hash:
{
"version": 1,
"approved": {
"223ba97aa0b710a0fb893f9c5699ca782b8649a1e8b39e499d27e0f1e05eed93": {
"id": "@solunify/iso",
"version": "1.0.0",
"capabilities": { "network": false, "storage": false },
"decidedAt": "2026-09-12T20:48:14.665Z"
}
},
"denied": {}
}
decide() always rewrites both the approved and denied entries for a hash together, so a hash only ever appears in one of the two objects at a time. Approving or denying an unrecognised hash isn't possible through the CLI or the API; both first look the hash up against the vault's currently resolved blocks (or, for plicine trust, every block visible to the vault at all).
Search and backlinks
vaults/<vault-hash>/index/documents.json is what GET /api/search and GET /api/backlinks answer from. It holds a version, a short hash of the vault's file list, the vault's title, and one entry per document:
{
"version": 3,
"files": "9f2b1c0d4e5a6b7c",
"vaultTitle": "Lorem notes",
"documents": [
{
"path": "welcome.md",
"title": "Welcome",
"size": 2481,
"mtimeMs": 1758013245123.4,
"fields": [
{ "kind": "title", "text": "Welcome", "line": 0 },
{ "kind": "heading", "text": "Blocks", "line": 24, "label": "blocks" },
{ "kind": "text", "text": "A block is a fenced code block whose name matches an installed block.", "line": 26 },
{ "kind": "block", "text": "Lorem rates", "line": 31, "label": "table.caption" }
],
"links": [{ "path": "pricing.md", "line": 26, "kind": "wikilink" }],
"unresolved": 0
}
]
}
A field is one searchable piece of the document with the source line it starts on, which is how a result can scroll to the element it matched in. kind says where it came from, and how much a match in it counts for when results are ranked: the document's title, a heading (whose label is its id, so a result can link straight to it), a paragraph or other element of text, or a block, which covers both a block's name and each of its props flattened to a path such as table.rows.0.1. links holds the documents this one links to, whether the link was written as a [[wikilink]], as a markdown link to a .md file, or inside a block's markdown; unresolved counts the [[links]] that matched no file, which is how the server knows to read a document again when the vault's files change.
size and mtimeMs are the file as it was when it was indexed. A document whose file still matches both isn't read again on start, so opening a vault only parses what changed since last time. The one exception is the vault's own INDEX.md, which goes by the vault's title while it has none of its own: vaultTitle is the title it was indexed under, and it's read again when the vault has been given another. Deleting the folder costs nothing but the next rebuild.
agents.json
The agent palette finds Claude Code, Kiro, Gemini CLI and Codex by itself. Any other agent that speaks the Agent Client Protocol goes in this file:
{
"agents": [
{ "id": "my-agent", "name": "My agent", "command": "my-agent", "args": ["acp"], "env": { "MY_AGENT_PROFILE": "work" } }
]
}
id is lower-case letters, digits and hyphens, and reusing a built-in id (claude, kiro, gemini, codex) replaces that agent. command is either a name looked up on PATH, as your terminal sees it, or an absolute path. args and env are optional. If the file doesn't parse, the palette says why and the built-in agents carry on working.
The list lives here and not in a vault on purpose. A vault arrives through a shared folder, and nobody you share one with should be able to pick a command that runs on your machine.
What's safe to delete
Most of the folder is cache. packages/, deps/, inline/ and vaults/*/pdf can be deleted at any time; they're rebuilt (recompiled, reinstalled, re-rendered) the next time they're needed. chrome/ is cache too, and the biggest thing in the folder at around 250 MB unpacked: deleting it (or running plicine chrome remove) costs a re-download on the next export, unless a browser is installed or PLICINE_CHROME is set, in which case it costs nothing at all. Deleting vaults/*/sidebar.json closes every folder in that vault's sidebar, and nothing else, and deleting vaults/*/index costs one rebuild the next time the vault is opened. Two things aren't cache. Deleting trust.json resets every approval, so every block goes back to pending and needs re-approving. Deleting vaults/*/storage deletes any data blocks have saved through globalThis.plicine.storage, which is real user data, not a cache, and won't come back.
Deleting library/ or community/ removes those installed blocks and themes from every vault that relies on them (rather than carrying its own copy in its .vault/blocks//.vault/themes/ folder), which will show up as "Unknown block" or a missing theme until they're reinstalled.
Deleting agents.json removes the agents you added; the built-in ones are found again. Deleting agent-settings.json forgets the models picked in the palette, so each agent goes back to starting a session on its own default. Deleting agent-palette.json brings the palette's note back. agent-cwd/ is made again whenever an agent starts.
Deleting dev/ discards every checkout, along with any local edits in them that haven't been pushed; nothing pushed is lost, since pushing writes straight into the vault. plicine block discard deletes one checkout the same way.
What the vault receives
Nothing generated lives in the vault. The single exception is blocks.lock, at .vault/blocks/blocks.lock, and even that is only written when resolving the vault's blocks actually produces a different lock than what's on disk (How blocks work covers when that happens). No cache, compiled output, or trust decision is ever written back into a vault folder.