File Format Reference

Last reviewed: 2026-07-04

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 four top-level keys (filters, formulas, properties, views). Adding any other top-level key makes Obsidian reject the whole file. All Plainva-specific data 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
okf_version: "0.1"
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. Two top-level fields:

FieldTypeMeaning
typestringWhat kind of document this is (Note, Daily Note, Project, …). The only field OKF actually requires.
okf_versionstringThe convention version the file was written against, e.g. "0.1". Quote it so YAML keeps it a string.

A file without type still opens fine; it is simply “not OKF-conformant”. A missing okf_version alone is not a violation. When you create a new note, adding type (and okf_version) is good practice. See OKF for the full rationale.

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

Presentation-only 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

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


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)

views[i].plainva — per view:

KeyValueMeaning
renderboard / calendar / timelinePlainva-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)

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

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.

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.

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.

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
okf_version: "0.1"
status: Open
project: "[[Project Alpha]]"
---
# Write proposal

Projects/Project Alpha.md

---
type: Project
okf_version: "0.1"
---
# 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

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

Do-not-touch and safety

See also