Skip to the document

vault.json and frontmatter

Every key vault.json and document frontmatter can set, their types and defaults, and how the two combine with a theme's own theme.json when they disagree.

vault.json

vault.json sits at .vault/vault.json and is optional: a missing file behaves exactly like {}. It's read fresh on every Workspace.refresh() (so the running app and CLI pick up edits without a restart).

Key Type Default Meaning
title string the folder name The vault's title, shown at the top of the sidebar, as the browser tab title and as the desktop window title. Changes show up live. An old name key (the earlier spelling) is reported as a warning rather than used
theme string "default" Theme id used when a document's frontmatter doesn't set theme
page "A4" or "Letter" none Page size used when a document's frontmatter doesn't set page and the theme doesn't declare one
layout "infinite" or "paginated" none Layout used when a document's frontmatter doesn't set layout
aliases object of string to string {} Short names for blocks, e.g. "quote": "@solunify/quote-table@^2"
publicUrl string none Base URL of a hosted copy of the vault; used to build the default link for a document (and its QR code) when frontmatter doesn't set link
credit boolean true Whether exports carry the "Made with Plicine · plicine.com" line, for documents whose frontmatter doesn't set credit. false takes it off every PDF and every page of an exported site. See The credit line
agentsMd boolean false Keep an AGENTS.md at the vault root, holding the same primer the agent palette sends, so an agent working from a terminal gets it with Plicine closed. See Using AI agents
values object {} Values for {{placeholders}} every document shares, such as the organisation's name or a default author. A document's frontmatter sets its own over them; see Values
settings object {} Parsed and kept on Vault.config.settings, but nothing in the app currently reads it
{
  "title": "Lorem Ipsum Ltd",
  "theme": "acme",
  "page": "A4",
  "layout": "infinite",
  "aliases": {
    "quote": "@solunify/quote-table@^2"
  },
  "publicUrl": "https://docs.loremipsum.example",
  "agentsMd": true,
  "values": {
    "organisation": "Lorem Ipsum Ltd",
    "author": "Dolor Sit"
  }
}

settings exists in the schema and is validated, but nothing built yet reads it back out.

Keys the app writes

The app's Vault settings screen writes seven of these keys: title, theme, page, layout, publicUrl, credit and agentsMd. aliases, values and settings are only ever edited in the file. The rules are the same for every write (Vault.setConfigValues and Vault.updateSettings in packages/core/src/vault.ts):

  • Only the keys that changed are written. Every other key stays as it was, in the same order, and the file keeps its indentation, its line endings and whether it ended with a newline. A key that's new to the file goes at the end.
  • Nothing is written when nothing would change, and nothing is written on open. A vault with no vault.json gets one the first time a setting is changed.
  • Unset and the default stay apart. Choosing a key's default again (theme: "default", layout: "infinite", credit: true, agentsMd: false, or an empty title or publicUrl) removes the key instead of writing the default. page has no default of its own, so A4 and Letter are both written, and The theme's removes the key.
  • A publicUrl has to start with http:// or https://, and a theme has to be installed.
  • A file that's invalid (see Invalid vault.json) is never written over. The settings screen shows the error instead, and the route answers 409.

agentsMd has two more writers: the Write AGENTS.md toggle in the palette's agent menu, and plicine agents-md --write / --remove, which set it and then write or remove the file to match. They write false for off where the settings screen removes the key; both mean the same. While it's on, Plicine keeps AGENTS.md in step whenever the vault's documents, blocks, themes or config change, and rewrites it only when its content would actually differ.

Values

values, folders and frontmatter fill the same {{placeholders}}, in layers, each over the ones before: the vault's values, then the frontmatter of each folder's INDEX.md from the outermost folder in, then the document's own frontmatter. A folder's values reach every document below it, however deep, and a folder further in adds to them or overrides them. Objects merge key by key, so a vault with client: { country: UK } and a folder with client: { name: Lorem } give client.name and client.country both; anything else, a list included, replaces what was there.

A folder page's frontmatter keys that set up the page itself (title, theme, page, layout, blocks, cover, link and credit) stay that page's and don't pass down. For the folder's own page, its frontmatter is the document layer, over the folders it's in.

To set a folder's values, open its page and add the keys to its frontmatter in Source view, as you would on any document.

The layers reach:

  • The theme's cover, header and footer templates, so a vault can set organisation once and every cover shows it, while one document sets its own author. See The .theme package.
  • Templates being filled, as their defaults: the layers under the template, its own frontmatter (leaving out any value that still holds a placeholder itself), then the data passed in.

GET /api/doc carries the merged result as values. A document's own text is never filled in place: its {{placeholders}} stay as written until a new document is made from it, so a file always reads the same in Plicine as in any other editor.

Invalid vault.json

If .vault/vault.json exists but isn't valid JSON, or is valid JSON that fails the schema above (wrong type for a key, page outside "A4"/"Letter", and so on), the vault falls back to the all-defaults config (theme: "default", everything else empty) and records the problem as Vault.configError, a string like .vault/vault.json: page: Invalid enum value.... That message is then surfaced as a warning on every document, in /api/vault's warnings list, and as an error against the file .vault/vault.json from plicine validate. While the file is in that state nothing in the app writes to it (Vault.configInvalid), so a fix made by hand is never lost under a setting changed in the app. A missing .vault/vault.json file is not an error; only a present-but-broken one is. A vault.json left at the vault root isn't read at all. Plicine says so and asks you to move it into .vault/: a warning in the app, an error from plicine validate.

Aliases

aliases maps a short name, the one used as a fence's info string in a document, to a full block spec: either a bare id ("@acme/price-grid", meaning any installed version) or an id with a semver range ("@acme/price-grid@^1.2"). Aliases are vault-wide and apply to every document that doesn't pin the same name itself. The target can be a core block ("contents": "@core/toc"); a range on a @core id is ignored, since core blocks are versioned with the app.

Frontmatter

Frontmatter is a YAML mapping between two --- lines (or ---/...) at the top of a document. The keys below are the ones the app reads; anything else you put there is kept as-is and passed through to theme templates (see The .theme package) as {{yourKey}}, over any value of the same name in vault.json's values.

Key Type Default Meaning
title string first # heading, or the file name Document title
theme string vault.json's theme Theme id for this document
page "A4" or "Letter" vault.json's page, then the theme's page, then "A4" Page size for this document
layout "infinite" or "paginated" vault.json's layout, then "infinite" How the app shows this document: one continuous page, or laid out in pages the way it prints
blocks object of string to string none Pins: for each fence name, the exact block spec to resolve it to, e.g. quote-table: "@solunify/quote-table@2.1.0"
cover boolean false true puts the theme's cover page at the front of this document's pages
link string vault.json's publicUrl joined with the document's path, if set URL printed (and turned into a QR code) at the end of the PDF
credit boolean vault.json's credit, then true false leaves the "Made with Plicine · plicine.com" line off this document's PDF and its page on an exported site; true puts it back in a vault that turned it off
---
title: Lorem ipsum proposal
theme: acme
client: Lorem Ipsum Ltd
page: A4
layout: paginated
cover: true
blocks:
  quote-table: "@solunify/quote-table@2.1.0"
---

client above isn't a key the app reads itself; it's only there because the cover and footer templates of the theme it picks (Acme, the example theme in Writing a theme) use {{client}}. The built-in theme's templates use it too.

Precedence

Theme: a document's own theme frontmatter wins; otherwise vault.json's theme ("default" if that's not set either); if the resolved id doesn't match any installed theme, the app warns (Theme "..." not found; using the default theme) and falls back to the theme built into the app (packages/core/theme-default/), not to vault.json's configured theme.

Page size: document frontmatter page, else vault.json's page, else the winning theme's own page (from its theme.json), else "A4".

Layout: document frontmatter layout, else vault.json's layout, else "infinite". Unlike theme and page size, there's no third source: a theme doesn't declare a layout.

Credit: document frontmatter credit, else vault.json's credit, else true. GET /api/doc carries the result as credit. A folder's INDEX.md sets it for its own page only, not for the documents below it.

The credit line

An export names Plicine once: "Made with Plicine · plicine.com". On a PDF it's one small line at the foot of the last page, linked to https://plicine.com/?ref=pdf, and only in the built-in theme. A theme carries it when its theme.json sets "credit": true, which no other bundled theme does, so a PDF needs both: a theme that carries the line and a credit that resolves to true. On a site made with plicine export-site it's under every page, linked to https://plicine.com/?ref=site, whatever the theme, because the footer belongs to the site's frame and not to the theme. The infinite layout in the app never shows it.

To take it off everywhere:

{
  "credit": false
}

To take it off one document:

---
credit: false
---

Turning it off costs nothing and changes nothing else.

Invalid frontmatter

Frontmatter is parsed as YAML first; a syntax error there (bad indentation, unterminated quote and so on) leaves the whole frontmatter object empty for that document and is reported as Frontmatter: <message>. Once parsed, the object above is validated as a whole: if any key has the wrong type (page: A5, layout: read, blocks not a mapping of strings, cover: "yes" instead of a boolean), the entire set of typed keys (title, theme, page, layout, blocks, cover, link, credit) is dropped for resolution purposes, not just the offending key, and a warning listing every failing field is added. Document resolution then behaves as if none of those keys were set: the vault's theme, page size, layout and credit apply, and there are no pins.

One exception: the cover page. Whether a document gets a cover is decided directly from the raw frontmatter value of cover (doc.doc.frontmatter.cover === true) rather than from the validated settings, so cover: true still adds the cover even on a document whose frontmatter fails validation elsewhere.

Unknown top-level frontmatter keys are never an error; they just aren't used for resolution and are only available to theme templates.

Pins and resolution order

A fence's name (quote-table in ```quote-table) resolves to an actual block in this order, per document:

  1. If frontmatter blocks pins that name to a spec ("@scope/id" or "@scope/id@range"), that spec is used.
  2. Otherwise, if vault.json's aliases maps that name, that spec is used.
  3. Otherwise, if the name itself starts with @, it's used directly as the spec.
  4. Otherwise, the name is matched against every installed block's alias field.

Once a spec (or a plain alias match) narrows things down to a set of candidate .block files, the vault's own .vault/blocks/ folder wins over the app-wide library, which wins over community-installed blocks; within the winning source, the highest matching semver version is chosen, unless blocks.lock already locked that exact name (or name=spec key, for a pinned name) to a hash that's still among the candidates, in which case the lock wins. See How blocks work for the full mechanics, including what happens when two installed blocks conflict.