apps/web/content/docs/guides/0.data.mdx
Char has no database. Every piece of data — sessions, transcripts, notes, contacts, settings — lives as plain files on your filesystem. Markdown files with YAML frontmatter for human-readable content, JSON files for structured data. This is a deliberate architectural choice: the filesystem is the data layer.
This means you can browse your data in Finder, back it up with any tool, sync it with git, or open your notes in Obsidian. Nothing is locked inside a proprietary format or opaque database.
The architecture has three layers:
When you edit a note in the UI, the change flows: UI → TinyBase store → Persister → fs-sync → disk. When a file changes on disk (e.g. you edited it externally), the flow reverses: disk → file watcher → Persister → TinyBase → UI.
There are two types of base directories: global and vault.
stable and nightly builds (staging and dev builds use different folders).~/Library/Application Support/hyprnote/~/.local/share/hyprnote/Contents:
models/stt/ — downloaded speech-to-text model filesstore.json — app state (onboarding status, pinned tabs, recently opened sessions, dismissed toasts, analytics preference, auth tokens)hyprnote.json — vault configuration (custom vault path if set)search/ — full-text search index (Tantivy)global base for backward compatibility.Contents:
sessions/ — one subdirectory per session, each containing:
_meta.json — session metadata (title, created date, participants)_memo.md — raw notes in Markdown with YAML frontmattertranscript.json — transcription data (words, timestamps, speakers)*.md — AI-generated enhanced notes (summaries, action items)attachments/ — file attachments.mp3 files — recorded audiohumans/ — contact and participant data (Markdown with frontmatter)organizations/ — organization data (Markdown with frontmatter)chats/ — chat conversation dataprompts/ — custom prompt templatessettings.json — app settingsHumans, organizations, and notes are stored as Markdown files with YAML frontmatter. For example, a contact file in humans/ looks like:
---
user_id: "usr_abc123"
name: "Alice Johnson"
emails:
- [email protected]
org_id: "org_xyz"
job_title: "Engineering Manager"
linkedin_username: "alicejohnson"
pinned: false
---
Personal notes about Alice go here.
The frontmatter holds structured fields; the body holds free-form Markdown content. This format is both machine-parseable and human-readable — you can open these files in any text editor or Markdown tool.
Enhanced notes (AI-generated summaries, action items) follow the same pattern with their own frontmatter fields:
---
id: "note_def456"
session_id: "sess_abc123"
template_id: "tmpl_summary"
position: 0
title: "Summary"
---
## Key Points
- Discussed Q1 roadmap priorities...
Session metadata (_meta.json), transcripts (transcript.json), and settings (settings.json) use JSON. For example, a session's _meta.json:
{
"id": "sess_abc123",
"user_id": "usr_abc123",
"created_at": "2025-12-15T10:30:00Z",
"title": "Q1 Planning",
"participants": [
{
"id": "part_1",
"user_id": "usr_abc123",
"session_id": "sess_abc123",
"human_id": "human_xyz",
"source": "calendar"
}
],
"tags": ["planning", "quarterly"]
}
Transcript files store word-level timestamps and speaker information:
<GithubCode url="https://github.com/fastrepl/char/blob/main/crates/fs-sync-core/src/types.rs#L83-L119" />Each session gets its own directory under sessions/, named by its UUID. Sessions can also be organized into user-created folders:
sessions/
├── a1b2c3d4-.../ (session at root)
│ ├── _meta.json
│ ├── _memo.md
│ ├── transcript.json
│ ├── _summary.md
│ ├── Action Items.md
│ ├── attachments/
│ │ └── screenshot.png
│ └── audio.mp3
├── work/
│ └── e5f6g7h8-.../ (session in "work" folder)
│ ├── _meta.json
│ └── ...
└── personal/
└── projects/
└── i9j0k1l2-.../ (session in nested folder)
├── _meta.json
└── ...
Here is how Char loads session content from disk — this shows exactly what files are read per session:
<GithubCode url="https://github.com/fastrepl/char/blob/main/crates/fs-sync-core/src/session_content.rs#L10-L110" />Persisters are the bridges between TinyBase (the in-memory store) and the filesystem. There are three persister factories, each designed for a different data shape:
Used for data stored as a single JSON file (e.g. settings.json). Reads the file into a TinyBase table on load, writes the full table back on save. Supports both polling and filesystem notification-based change detection.
Used for entities stored as individual Markdown files in a directory (e.g. humans/, organizations/, prompts/). Each entity is one .md file. The frontmatter maps to TinyBase row fields; the body maps to a content field (like memo).
Used for complex entities that span multiple TinyBase tables (e.g. sessions). A single session directory produces rows in the sessions, transcripts, enhanced_notes, mapping_session_participant, tags, and mapping_tag_session tables. This persister coordinates loading and saving across all these tables atomically.
All three factories share a common collector layer that handles batching write operations, file-change listening, and orphan cleanup.
Char watches the vault directory for external changes. When files are modified outside the app (e.g. you edit a Markdown file in another editor), the persister detects the change and reloads the affected data into TinyBase, which updates the UI reactively.
The file watcher uses two strategies:
To avoid reacting to its own writes, the app marks paths it has just written and ignores notifications for those paths.
When data is deleted in the UI, the persister removes the corresponding files from disk. A safeguard prevents accidental mass deletion: if the number of items to keep drops below 50% of what's on disk (with a minimum threshold of 5 items), cleanup is skipped and a warning is logged. This protects against data loss from load failures being misinterpreted as deletions.
The actual file I/O runs in Rust via the fs-sync Tauri plugin. It provides commands for:
The app state persisted in store.json is defined by this enum — nothing else is stored there:
Application logs are stored in the system app log directory as rotating files (app.log, app.log.1, etc.).
For details on what data leaves your device, see AI Models & Data Privacy.