Skip to content

Data Model

TypeMD uses a dual-storage design: Markdown files are the source of truth, and a SQLite database provides fast indexing and search. This page covers the technical details of the indexing layer.

For user-facing file structure details, see File Structure.

The fundamental design principle is that files are always the source of truth. The SQLite index is an acceleration layer that can be rebuilt from files at any time. If the index is deleted or corrupted, no data is lost — opening the vault will rebuild it.

This design enables:

  • Version control with Git (only Markdown and YAML files matter)
  • Manual editing with any text editor
  • Syncing via file-based tools (Dropbox, iCloud, Syncthing)
  • Portability between machines without database migration

The index is stored at .typemd/index.db and contains:

  • Object metadata — type, filename, and all frontmatter properties
  • Wiki-link records — extracted from Object body content, used for backlink tracking
  • Full-text search index — powered by FTS5, covering filenames, properties, and body content

The index file is not meant to be edited directly. It is managed entirely by TypeMD.

The index is automatically synced in these situations:

  • Vault open — every time a vault is opened, TypeMD walks all Object files and syncs the index
  • CLI/TUI operations — creating, saving, or deleting Objects updates the index immediately

The sync process is handled by the Projector component, which walks all Object files via the repository and upserts entries into the index. See Architecture for how the Projector fits into the system.

TypeMD provides two query paths:

The query pipeline uses structured FilterRule conditions to filter Objects. Each rule specifies a property, an operator, and a value. Queries run against the SQLite index for performance, returning lightweight ObjectResult projections rather than full Object entities. If the SQLite index is unavailable, queries automatically fall back to filesystem scanning with in-memory filter matching using the same operators.

Filter rules are used programmatically through Vault.QueryObjects() and in view configurations (types/<name>/views/<view>.yaml).

Use tmd search to search across filenames, properties, and body content. Powered by SQLite FTS5, with automatic fallback to case-insensitive substring matching when the index is unavailable:

Terminal window
tmd search "concurrency"
tmd search "golang" --json

In the TUI, press / to enter search mode. Results are filtered in real-time. Press Esc to clear results and return to the full list.

An optional configuration file at .typemd/config.yaml provides vault-level settings. The file uses interface-layer namespacing:

.typemd/config.yaml
date_format: "DD/MM/YYYY"
datetime_format: "DD/MM/YYYY HH:mm:ss"
cli:
default_type: page

Currently supported settings:

KeyDescription
date_formatDisplay format for date properties using tokens YYYY, MM, DD (default: YYYY-MM-DD). Storage format is unchanged.
datetime_formatDisplay format for datetime properties and system timestamps using tokens YYYY, MM, DD, HH, mm, ss (default: YYYY-MM-DD HH:mm:ss). Storage format is unchanged.
cli.default_typeDefault object type for tmd object create when type argument is omitted

The config file is loaded during vault open. If the file is missing or empty, all settings use their zero values (no error). Invalid YAML produces an error.

Type schemas can opt into name uniqueness by setting unique: true. When enabled, TypeMD prevents creating multiple Objects of the same type with identical name values.

types/person/schema.yaml
name: person
unique: true # only one person per name
properties:
- name: role
type: string

Uniqueness is enforced at creation time by checking the index for existing Objects with the same type and name. It is also validated by tmd type validate. The built-in tag type has unique: true enabled by default.