File Format Reference

Last reviewed: 2026-08-21

This page is the precise, on-disk contract for every file in a Plainva vault. It is written so that a tool — or another program, script or AI assistant — can read and safely edit vault files directly, without going through Plainva’s user interface. If you only use the app, you never need this page; the other guide pages cover normal use.

Everything here is plain UTF-8 text. Notes are Markdown with YAML frontmatter; databases are YAML. Nothing is proprietary and nothing is hidden.

Golden rules (read first)

  1. The note is the source of truth. A .base is only a view. Property values live in the frontmatter of the individual notes — never inside the .base. To change a value, edit the note.
  2. Notes stay Obsidian-native. In note frontmatter, only ever write plain scalars and lists (string, number, boolean, ISO date, YAML list). Never write a nested object or an “active/selected” flag into a note.
  3. A .base uses only Obsidian’s five top-level keys (filters, formulas, properties, summaries, views). Obsidian does not understand any other top-level key. That is why everything Plainva-specific goes under nested plainva: sub-keys.
  4. Preserve what you do not understand. Unknown keys must survive a read/write round-trip unchanged. Do not “clean up” keys you do not recognize.
  5. Write UTF-8 without BOM, with LF line endings.

The vault at a glance

A vault is an ordinary folder. The file types you will meet:

FileWhat it isEditable as text
*.mdA note: YAML frontmatter + Markdown bodyYes
*.baseA database view over notes (YAML)Yes
index.mdA folder’s managed table of contents (reserved name)Yes, with care — see index.md
log.mdReserved name, currently unusedLeave alone
images, PDFs, …AttachmentsNo (binary)
.plainva/Plainva’s internal folder (backups, state)No — never touch

Reserved names index.md and log.md are never regular notes; do not create ordinary content under those names.


Notes (.md)

A note is a Markdown file. An optional YAML frontmatter block (between two --- lines) at the very top holds its properties; the Markdown body follows.

---
type: Note
tags: [project, active]
status: In progress
due: 2026-07-20
plainva:
  icon: "🚀"
  header_color: "#2f6f6f"
---
# My Project

A **bold** thought that links to [[Another Note]].

- [ ] First task

OKF frontmatter fields

Plainva follows OKF (Open Knowledge Format), a minimal convention — currently version 0.2. One top-level field is required, everything else is optional:

FieldTypeMeaning
typestringWhat kind of document this is (Note, Daily Note, Project, …). The only field OKF actually requires.
generated, verified, sources, stale_after, statussee belowThe trust and lifecycle fields of OKF 0.2 — all optional; see “Trust and lifecycle” right after this section.
okf_versionstringRoot index.md only: the version of the convention the whole vault follows, e.g. "0.2" (quoted so YAML keeps it a string). Plainva used to write this field into individual notes up to OKF 0.1; since 0.2 it no longer does. Existing entries are not an error — the bundle upgrade can remove them on request.

A file without type still opens fine; it is simply “not OKF-conformant”. An okf_version that is missing from a note — or still present in one — is not a violation. When you create a new note, adding type is good practice — nothing more is needed. See OKF for the full rationale.

Trust and lifecycle (OKF 0.2)

OKF 0.2 adds five optional top-level fields with which a note says where it came from, whether someone reviewed it, and whether it still holds. Plainva reads all of them; it writes them under fixed rules (see below).

FieldShapeMeaning
generatedobject { by, at }Who produced the note and when. at is an ISO 8601 instant in UTC (2026-08-21T10:11:12Z).
verifiedlist of { by, at }Who reviewed the note. A list, because every review is appended — the review history stays; the newest entry counts.
sourceslist of { resource, id?, title?, … }What the note was made from. resource is an identifier (a URL, mid:<Message-ID> of an email, a file path); further keys are allowed.
stale_afterdate (2026-12-31) or instantFrom when the content counts as stale. Pure information — Plainva shows a hint and changes nothing.
statusdraft | stable | deprecatedThe lifecycle state. Exactly these three values; draft and deprecated appear as a badge, stable stays silent.

Actors (by in generated and verified) follow a convention: a person is human:<name>, a program is <producer>/<version>. Plainva writes plainva-import/<version>, plainva-mail-capture/<version> and plainva-task-sync/<version> — and, for a review, human:<your name>.

Example of an imported, later reviewed note:

---
type: Note
generated:
  by: plainva-import/0.6.7
  at: 2026-08-21T10:11:12Z
sources:
  - resource: https://example.org/article
    title: Article
verified:
  - by: human:Marco
    at: 2026-08-22T08:00:00Z
status: stable
stale_after: 2027-08-21
---

Write rules — who sets what:

Property value serialization

Each frontmatter key is one property. Write the value in the native YAML form for its type:

Property typeYAML formExample
Textscalar stringtitle: Hello
Numbernumberpriority: 3
Checkboxbooleandone: true
DateISO date stringdue: 2026-07-20
Date & timeISO datetime stringat: 2026-07-20T14:30:00
ListYAML list of stringsauthors: [Ada, Alan]
TagsYAML list of stringstags: [project, active]
Select / Statussingle scalar stringstatus: Done
Multi-selectYAML list of stringslabels: [urgent, later]
URL / Email / Phonescalar stringsite: https://example.org
Relation (single)wiki-link stringproject: "[[Project Alpha]]"
Relation (multiple)YAML list of wiki-link stringsrelated: ["[[A]]", "[[B]]"]

The “active” value of a Select/Status property is just that plain scalar. The palette of allowed options and their colors do not live in the note — they live in the governing .base (see Options and colors). This keeps the note 100 % Obsidian-native.

Quote wiki-link values ("[[X]]"). Unquoted [[X]] is a YAML flow sequence and will not parse as you intend.

The plainva: namespace in notes

Plainva-specific note extras are bundled under a single plainva: key so other editors can ignore them:

KeyValueMeaning
iconemoji grapheme, or lucide:<kebab-name>Document icon (Notion-style)
icon_colorhex color (#rgb / #rrggbb / #rrggbbaa)Tint for a lucide: icon (emoji ignore it)
header_colorhex colorFull-width header stripe
tasksfalseExclude this note’s checkboxes from the Tasks view
templateForlist of wiki links to .base filesAssigns a template to the listed databases (meaningful only for notes inside the template folder)
pimmapping (see below)Anchor tying the note to an external calendar event, task or email

All of these are optional. If you write none of them, omit the plainva: key entirely. Invalid values are ignored on read, never treated as an error.

pim is the anchor of the PIM integrations (see Calendar & external tasks and Email capture). It is a small mapping written by Plainva when a note mirrors an external object: uid plus where it came from, and depending on the kind calendar (meeting notes), kind: task + list (synced tasks) or kind: email + mailbox (captured mails). Tools should preserve it unchanged; deleting the anchor merely detaches the note from its remote object (nothing is deleted remotely by that). Example:

plainva:
  pim:
    kind: task
    uid: MTIzNDU2
    list: MDEyMzQ1
    provider: caldav
    identity: https://cloud.example.com:alice
    account: 3f9c21ab

What describes the origin. For a task, uid and list are what count — a uid is unique at ONE provider, not across two. provider (google, microsoft, caldav) and identity (the verified account identity, where the provider offers one) narrow it further, and both survive a reconnect. account is the LOCAL account id: Plainva still writes it so older versions can read the anchor, but no longer compares it — it is minted fresh on every connect, which is precisely why a reconnected account used to import its tasks a second time. If you write anchors yourself, set uid and list; provider/identity are recommended, account is not needed.

templateFor is the field contract of the template assignment (see databases): on a note inside the template folder it lists the databases whose Entry menu shows the template by default. Values are whole wiki links including the .base extension — bare ("[[Tasks.base]]" matches the file of that name in any folder, so it survives pure folder moves) or path-qualified ("[[Projekte/Tasks.base]]" matches exactly that path). Plainva writes bare links and only qualifies when two same-named .base files exist. A scalar instead of a list is tolerated. When an entry is created from the template, templateFor — unlike the other plainva: keys — is not copied into the new note.


Dependencies (blockedBy)

An ordinary note property — not in the plainva: namespace — following RFC 9253, the vocabulary the TaskNotes plugin also writes:

blockedBy:
  - uid: "[[Projects/Rollout]]"   # wiki link to the predecessor
    reltype: FINISHTOSTART        # RFC 9253 vocabulary
    gap: P1D                      # optional ISO-8601 lag

Databases (.base)

A .base file is YAML. It stores a view over notes — which notes (sources), how to show them (views), how to filter and sort, and the column schema. It stores no note values. The format is compatible with Obsidian’s Bases plugin.

Hard rules — break one and Obsidian rejects the whole file

Plainva itself heals older files that violate the last two rules the next time it saves them, but a tool writing directly must get them right.

Property identifiers: when to use the note. prefix

This trips people up, so it is explicit:

WhereFormExample
Keys of the properties: mapprefixednote.status, file.name
A view’s order: listprefixed[file.name, note.status]
A view’s sort[].propertyprefixednote.due
Inside filter expressionsbarestatus == "Done"
Inside plainva sub-keys (groupBy, dateField, endField, subItemsProperty)baregroupBy: status

Rule of thumb: the Obsidian-facing structural fields use note.<key> (and file.<x> for built-ins like file.name, file.folder, file.mtime); everything inside a filter formula or a plainva block uses the bare frontmatter key.

Top-level keys

The plainva: sub-key map

Everything Plainva-specific is namespaced. Three locations:

properties[<note.key>].plainva — per column:

KeyValueMeaning
inputone of the input types belowThe column’s field type
optionslist of option objectsCurated values for select/status/multiselect
relationBasevault-relative .base pathRelation target database (see Relations)
relationLimitoneCardinality: single link. Omit for unlimited.
reverseOf{ base, property }Marks a computed reverse-relation column (no input)
rollup{ through, of, fn, where }Marks a computed rollup column (no input) — see below

views[i].plainva — per view:

KeyValueMeaning
renderboard / calendar / timeline / graph / pinboardPlainva-only view kind (see below)
groupBybare property keyBoard grouping column
dateFieldbare property keyCalendar/timeline start date
endFieldbare property keyTimeline end date
coverImagebare property keyGallery cover-image property
subItemsPropertybare property keySelf-relation parent column for sub-item nesting
widthsmap of id → pxColumn widths
dateFormatstringPer-view date format (default is implicit — omit it)
pinboardOrderlist of vault-relative pathsManual order of the UNPINNED pinboard cards
pinboardPinnedlist of vault-relative pathsPinned cards; the list order is the section order
pinboardFilterBytags or a bare multi-select keyLabel source of the pinboard’s chip bar (tags is implicit — omit it)

Besides the plainva block, a view may carry a native views[i].filters object — the per-view property filters (same single-rooted and/or/not grammar as the file-level filters). Plainva stores property filter rules here, one set per view, so each view filters independently; the file-level filters then keeps only the sources. Obsidian applies views[i].filters per view natively.

views[0].plainva — file-wide keys, allowed only on the first view:

KeyValueMeaning
fileIconColorhex colorTint of the database icon (tree/tabs/header)
newItemFoldervault-relative folderWhere the “New” button stores new items
newItemTemplatevault-relative .md pathDefault template for new items
contextFilterslist of bare property keysSelf-reference (“this note”) filters — see below
taskList"<account id> <list id>"Provider task list new tasks are also created in — see below

contextFilters is Plainva’s equivalent of Notion’s “this page” filter. Each entry is a property key; when the database is embedded in a note, its rows are scoped to that host note through that property (resolved via the link index — an owning/plain-link property matches rows pointing at the host, a computed reverse column matches what the host points at). It is deliberately not written into the native filters, so Obsidian ignores it and shows all rows; opened standalone in Plainva it is also dropped (no host) and shows all rows. Multiple entries AND-combine.

taskList names the task list where a task created in Plainva is also created at the provider (Google Tasks, iCloud reminders, Microsoft). The value is the account id and the list id separated by the first space — a CalDAV list id may itself contain spaces. Without the key a new task stays a plain note. If the value no longer resolves (account removed, list deleted), Plainva behaves as if the key were absent rather than guessing a different list. Obsidian ignores it.

Input types

plainva.input is one of:

text  number  checkbox  date  datetime
select  status  multiselect
list  tags  url  email  phone
relation

A computed reverse column has no input — it is identified solely by reverseOf.

Rollups

A rollup column has no input either. It computes a value from the notes a link column of THIS database points at:

properties:
  note.offen:
    displayName: Open
    plainva:
      rollup:
        through: aufgaben      # a relation OR reverse column of this database
        of: status             # a property of the linked notes
        fn: countWhere
        where:
          op: "!="             # ==  !=  contains  notContains  >  <  >=  <=
          value: Erledigt

Options and colors

Select/Status/Multi-select columns may carry a curated option list. Each option:

options:
  - value: Open          # required
    color: amber         # optional palette name (see below)
    group: Active        # optional; STATUS only — orders options into stages
  - value: Done
    color: green
    group: Closed

color is a palette name, not a CSS color. Valid names: gray, teal, blue, green, amber, coral, purple, pink. An unknown color falls back to a value-derived color.

View types

views[i].type on disk is a native Obsidian type. Plainva-only renders are written as type: table plus a plainva.render hint, so Obsidian degrades them to a plain table:

You wantOn-disk typeplainva.render
Tabletable
Listlist
Gallerycards
Boardtableboard
Calendartablecalendar
Timelinetabletimeline

Filters

filters selects which notes are in the database and narrows them.

Source conditions decide membership:

Multiple sources are just multiple entries. No filters at all = every note in the vault.

Where property conditions live: at the file level, filters applies to every view. Plainva instead stores property filter rules per view in views[i].filters (same single-rooted structure) and keeps only the sources at the file level, so each view can filter independently. Both are valid Obsidian; a tool may write either. A legacy file with property conditions at the file level still works — Plainva distributes them into each view on the next save.

Property conditions use bare property names and these operators:

OperatorExpression
equalsstatus == "Done"
not equalsstatus != "Done"
containscontains(labels, "urgent")
does not contain!contains(labels, "urgent")
greater / lesspriority > "2", priority < "5"
at least / at mostpriority >= "2", priority <= "5"
is emptystatus == ""
is not emptystatus != ""

Structure (single-rooted!): one of and / or / not, whose entries are condition strings — or one level of nested {and:[...]} / {or:[...]} group objects (Notion-style groups). Example combining a source, a condition and an OR group:

filters:
  and:
    - 'file.folder == "Projects"'
    - 'status != "Done"'
    - or:
        - 'priority == "1"'
        - 'priority == "2"'

A complete annotated .base

filters:
  and:
    - 'file.folder == "Projects"'          # source: notes in the Projects folder
properties:
  note.status:                             # column id is note.-prefixed
    displayName: Status                    # optional Obsidian column label
    plainva:
      input: status
      options:
        - value: Open
          color: amber
          group: Active
        - value: Done
          color: green
          group: Closed
views:
  - type: table                            # first view: also carries file-wide keys
    name: All projects                     # every view needs a name
    order: [file.name, note.status]        # order uses note.-prefixed ids
    plainva:
      fileIconColor: "#2f6f6f"
      newItemFolder: Projects
  - type: table                            # a board is a native table + render hint
    name: Board
    plainva:
      render: board
      groupBy: status                      # groupBy uses the BARE key

Relations (the two-sided contract)

A relation links notes to each other. This is the most error-prone thing to author by hand, because it spans three places. Get all three consistent.

  1. The value lives in the source note’s frontmatter, as a wiki-link (or a list of them):

    ---
    type: Task
    project: "[[Project Alpha]]"
    ---
  2. The source .base declares the relation column (relationBase = the target database; relationLimit: one for a single link):

    properties:
      note.project:
        plainva:
          input: relation
          relationBase: Projects.base
          relationLimit: one
  3. The target .base may show the reverse with a computed column. Its values are not stored anywhere — they are derived from the source notes’ links:

    properties:
      note.tasks:
        plainva:
          reverseOf:
            base: Tasks.base       # the source .base (vault-relative path)
            property: project      # the BARE source property key

Worked example: Tasks ↔ Projects

Tasks.base

filters:
  and:
    - 'file.folder == "Tasks"'
properties:
  note.status:
    plainva:
      input: status
      options:
        - value: Open
          color: amber
        - value: Done
          color: green
  note.project:
    plainva:
      input: relation
      relationBase: Projects.base
      relationLimit: one
views:
  - type: table
    name: All tasks
    order: [file.name, note.status, note.project]

Projects.base

filters:
  and:
    - 'file.folder == "Projects"'
properties:
  note.tasks:
    plainva:
      reverseOf:
        base: Tasks.base
        property: project
views:
  - type: table
    name: All projects
    order: [file.name, note.tasks]

Tasks/Write proposal.md

---
type: Task
status: Open
project: "[[Project Alpha]]"
---
# Write proposal

Projects/Project Alpha.md

---
type: Project
---
# Project Alpha

Result: in Projects.base, the computed tasks column of Project Alpha lists “Write proposal”, because that task’s project links back to it. Note that Project Alpha.md has no tasks: key — the reverse side is computed, never stored.

Relation DON’Ts

Self-relations and sub-items

For a relation whose target is the same database, point relationBase at that same .base. To nest children under parents in a table view, set views[i].plainva.subItemsProperty to the bare parent-relation key. Cycles are handled; with sub-items off, the rows stay flat and the values are kept.


index.md (folder table of contents)

index.md is a reserved name for a folder’s table of contents.

If you are generating a folder overview by hand, the safe choice is to not add the marker — then Plainva will never overwrite it.


Graph views (plainva.render: "graph")

A graph view is stored like every non-native view: type: table plus the render hint. Its options live in the SAME views[i].plainva namespace:

views:
  - type: table
    name: Net
    plainva:
      render: graph
      graphEdges: [projekt]        # relation property keys drawn as edges
      graphColorBy: status         # select/status property -> node color
      graphSizeBy: prio            # number property -> node size
      graphShowExternal: true      # include relation targets outside the view
      graphShowIncoming: true      # include relations from OTHER databases pointing in (e.g. a project's tasks)

All graph option keys are optional; omit them entirely when unset. Obsidian renders the same file as a plain table and must not error.

A board view (plainva.render: "board") may additionally carry views[i].plainva.boardColumnOrder — a list of group-column keys (__UNGROUPED__ marks the no-value column) that remembers a manual column order. Select/Status boards instead reorder the property’s options. Omit the key when unset.

The pinboard view (plainva.render: "pinboard")

A pinboard is stored like every non-native view: type: table plus the render hint. Its keys live in the same views[i].plainva namespace:

views:
  - type: table
    name: Pinboard
    plainva:
      render: pinboard
      pinboardOrder:                  # manual order of the unpinned cards
        - "Notes/Groceries.md"
      pinboardPinned:                 # pinned; list order = section order
        - "Notes/Idea.md"
      pinboardFilterBy: note.labels   # label source of the chip bar; omit = tags

Rules: pinned paths are not repeated in pinboardOrder. Cards in neither list render on top, newest first (creation time). Entries whose file no longer exists or left the source set are ignored and cleaned up on the next save. When a note is renamed or moved, Plainva retargets the paths in both lists automatically; external tools must do the same. Obsidian ignores the keys and shows the view as a table.

Do-not-touch and safety

See also