Skip to content

Database & Schema

Engine

SQLite via Bun’s built-in bun:sqlite + Drizzle ORM. PostgreSQL is supported as an optional override via the DATABASE_URL env var.

The database is opened in backend/src/db/index.ts. On a default install the file lives at data/app.db (relative to the repo root), with journal_mode = WAL, foreign_keys = ON, and busy_timeout = 5000.

The full schema is defined in a single file, backend/src/db/schema.ts (one file, not a schema/ directory). All tables are declared with sqliteTable(...).


Migration Approach

There are two layers, run belt-and-suspenders:

  1. backend/src/db/schema.ts is the Drizzle schema (table types used by queries).
  2. runMigrations() in backend/src/db/index.ts is the authoritative runtime migrator. It runs on every boot and issues idempotent CREATE TABLE IF NOT EXISTS / ALTER TABLE ... ADD COLUMN statements (via an addColumn() helper that swallows duplicate column name).

The generated SQL files under backend/src/db/migrations/ exist but are not the source of truth: the journal stops at 0016, so the Drizzle migrator does not apply newer migrations on a fresh DB. Every new table, column, or index must be mirrored as an idempotent statement inside runMigrations() or it will never exist on a fresh install.

Do not rely on bun run db:generate / drizzle-kit to ship a change. Edit schema.ts and add the matching idempotent statement to runMigrations().


Tables by Subsystem

The schema declares ~50 tables. Grouped by area:

Auth & users

TablePurpose
usersUser accounts (username, role)
sessionsSession tokens (SHA-256 token_hash, expires_at)
profile_pinsArgon2id PIN hashes per profile
user_preferencesPer-user key/value preferences (nav, home layout, highlights, etc.)
app_settingsApp-wide key/value settings (selected models, installed-component ledger, pepper)

Chat, memory & projects

TablePurpose
conversationsChat sessions
messagesIndividual messages (role, content, tool calls/results)
projectsProject grouping for conversations
entitiesExtracted entities for memory
memoriesLong-term memories (embeddings)
memory_episodesEpisodic memory records

Companions & voice

TablePurpose
charactersCompanion definitions (personality, avatar config, voice, category)
character_user_grantsPer-user companion access
user_charactersActive/owned companion state per user
voice_samplesRecorded voice samples (cloning / F5)
wake_word_catalogTrained wake-word models

Tools & permissions

TablePurpose
tool_global_configAdmin-level tool enablement/config
tool_user_configPer-user tool config
tool_user_permissionsPer-user tool grants
ha_user_grantsPer-user Home Assistant control scopes

Image generation

TablePurpose
generated_imagesGenerated image records (prompt, params, is_adult, path)
image_lora_categoriesLoRA category taxonomy
image_lorasLoRA metadata (trigger tokens, when_to_use, is_adult)
image_lora_user_category_grantsPer-user category access
image_lora_user_lora_grantsPer-user LoRA access
analysis_resultsVLM vision-analysis output (structured JSON)

Music

TablePurpose
music_tracksGenerated/stored music tracks

Offline library, maps & bookmarks

TablePurpose
zim_archivesZIM archive registry (path, category, enabled)
map_regionsMap region registry (bounds, pmtiles/routing paths)
maps_saved_pinsUser-saved map pins
maps_poi_enrichmentsEnriched POI metadata
bookmarksGlobal (admin) + personal bookmarks (user_id null = global)

Home inventory

TablePurpose
home_devicesTracked devices/appliances (make, model, serial, location, warranty)
home_service_logService/maintenance records
home_device_filesCached manuals/files per device
home_device_linksLinks per device

Videos & podcasts

TablePurpose
yt_subscriptionsYouTube channel subscriptions (the yt_* names predate the multi-source Videos app)
yt_videosCached YouTube video metadata
yt_channel_cacheChannel metadata cache
yt_downloadsDownloaded YouTube video records
yt_watch_statePer-user watch progress (YouTube)
yt_collectionsCross-source Watch Later / Liked collections
video_followsFollowed creators on non-YouTube sources (Reddit/TikTok/Vimeo)
video_itemsFeed cache for followed creators’ uploads
video_savesOffline saves for non-YouTube sources
video_watch_statePer-user watch progress (non-YouTube sources)
podcast_showsPodcast show definitions
podcast_episodesGenerated/imported episodes
podcast_episode_sourcesSource links per episode (reverse-link to the source video)
podcast_suggestionsSuggested episode topics
podcast_watch_statePer-user listen progress

System

TablePurpose
download_jobsDurable background download/install queue (see Boot & Feature System)
notificationsUser/admin notifications (user_id null = admin-targeted)

Conventions

  • Schema lives in a single file: backend/src/db/schema.ts.
  • All tables are sqliteTable(...); the Drizzle dialect is bun-sqlite.
  • Many key/value needs are served by app_settings (app-wide) and user_preferences (per-user) rather than dedicated tables.
  • Add a new table or column by editing schema.ts and mirroring it as an idempotent statement in runMigrations().