Meetily SQLite Database Schema for Meetings, Transcripts, and Summaries

Meetily stores all persistent meeting data, transcripts, and AI summaries in a single SQLite database that is created and migrated using SQL files under frontend/src-tauri/migrations.

Meetily is an open-source meeting assistant that relies on a SQLite database schema for meetings, transcripts, and summaries to persist every recording and AI-generated result locally. Defined by SQL migrations in the Tauri backend, this schema maintains strict referential integrity between meetings, their transcripts, and asynchronous summarisation jobs.

Database Overview and Migration Files

The database is initialized and versioned through numbered migration scripts in the frontend/src-tauri/migrations directory. The core schema is established in frontend/src-tauri/migrations/20250916100000_initial_schema.sql, which creates six primary tables. Subsequent migrations such as frontend/src-tauri/migrations/20250920155811_add_openrouter_api_key.sql and frontend/src-tauri/migrations/20251223000000_add_meeting_notes.sql incrementally extend the schema without breaking existing data.

All core tables use TEXT as the primary key type and store timestamps as TEXT for SQLite portability. Foreign-key constraints with ON DELETE CASCADE ensure that removing a meeting automatically cleans up its related transcripts, summary processes, and transcript chunks. The Rust database wrapper in frontend/src-tauri/src/database/mod.rs opens the connection and runs migrations on startup, while query helpers are provided in frontend/src-tauri/src/database/queries.rs.

Core Tables in the Meetily SQLite Schema

meetings

The meetings table is the root entity for every session created in the app. It is defined in frontend/src-tauri/migrations/20250916100000_initial_schema.sql as follows:

CREATE TABLE IF NOT EXISTS meetings (
    id TEXT PRIMARY KEY,
    title TEXT NOT NULL,
    created_at TEXT NOT NULL,
    updated_at TEXT NOT NULL
);

Each row represents a single meeting with a unique text identifier, a mandatory title, and timestamp tracking for creation and last update.

transcripts

The transcripts table stores the raw output from Whisper and any post-processing results. It references meetings(id) through a foreign key that cascades on deletion:

CREATE TABLE IF NOT EXISTS transcripts (
    id TEXT PRIMARY KEY,
    meeting_id TEXT NOT NULL,
    transcript TEXT NOT NULL,
    timestamp TEXT NOT NULL,
    summary TEXT,
    action_items TEXT,
    key_points TEXT,
    FOREIGN KEY (meeting_id) REFERENCES meetings(id) ON DELETE CASCADE
);

In addition to the full transcript text, this table reserves nullable columns for summary, action_items, and key_points that may be populated after LLM processing.

summary_processes

Async summarisation state is tracked in summary_processes, keyed directly by meeting_id. This design guarantees a one-to-one relationship between a meeting and its current summarisation job:

CREATE TABLE IF NOT EXISTS summary_processes (
    meeting_id TEXT PRIMARY KEY,
    status TEXT NOT NULL,
    created_at TEXT NOT NULL,
    updated_at TEXT NOT NULL,
    error TEXT,
    result TEXT,
    start_time TEXT,
    end_time TEXT,
    chunk_count INTEGER DEFAULT 0,
    processing_time REAL DEFAULT 0.0,
    metadata TEXT,
    FOREIGN KEY (meeting_id) REFERENCES meetings(id) ON DELETE CASCADE
);

Columns such as status, chunk_count, and processing_time allow the UI to report progress and diagnose failures for long-running LLM operations.

transcript_chunks

Before sending text to the LLM summariser, Meetily splits transcripts into manageable pieces. The transcript_chunks table persists this chunked representation:

CREATE TABLE IF NOT EXISTS transcript_chunks (
    meeting_id TEXT PRIMARY KEY,
    meeting_name TEXT,
    transcript_text TEXT NOT NULL,
    model TEXT NOT NULL,
    model_name TEXT NOT NULL,
    chunk_size INTEGER,
    overlap INTEGER,
    created_at TEXT NOT NULL,
    FOREIGN KEY (meeting_id) REFERENCES meetings(id) ON DELETE CASCADE
);

Fields like chunk_size, overlap, and model_name provide reproducibility by recording exactly how the text was segmented and which model configuration was active at the time.

settings

Global application configuration lives in the settings table. The initial migration defines provider credentials and model selections:

CREATE TABLE IF NOT EXISTS settings (
    id TEXT PRIMARY KEY,
    provider TEXT NOT NULL,
    model TEXT NOT NULL,
    whisperModel TEXT NOT NULL,
    groqApiKey TEXT,
    openaiApiKey TEXT,
    anthropicApiKey TEXT,
    ollamaApiKey TEXT
);

This row typically uses a singleton id value such as 'default' and stores API keys for Groq, OpenAI, Anthropic, and Ollama.

transcript_settings

For per-meeting overrides, Meetily provides the transcript_settings table. This allows individual transcriptions to use different providers or API keys without changing the global configuration:

CREATE TABLE IF NOT EXISTS transcript_settings (
    id TEXT PRIMARY KEY,
    provider TEXT NOT NULL,
    model TEXT NOT NULL,
    whisperApiKey TEXT,
    deepgramApiKey TEXT,
    elevenLabsApiKey TEXT,
    groqApiKey TEXT,
    openaiApiKey TEXT
);

Schema Evolution with Later Migrations

The Meetily SQLite schema is not static. Beyond the initial six tables, later migrations introduce new functionality.

The migration frontend/src-tauri/migrations/20250920155811_add_openrouter_api_key.sql extends the settings table with an openRouterApiKey column for OpenRouter integration. Another migration, frontend/src-tauri/migrations/20251223000000_add_meeting_notes.sql, introduces a meeting_notes table designed for user-taken markdown or JSON notes. These incremental changes are applied automatically by the Rust migration runner in frontend/src-tauri/src/database/mod.rs, ensuring that existing installations upgrade seamlessly.

Rust Examples for Database Operations

Meetily's Tauri backend interacts with the schema through standard SQLite queries executed via the Rust rusqlite interface. Below are common patterns extracted from the codebase.

Inserting a New Meeting

let sql = r#"
    INSERT INTO meetings (id, title, created_at, updated_at)
    VALUES (?1, ?2, datetime('now'), datetime('now'));
"#;
db.execute(sql, (&meeting_id, &title))?;

Adding a Transcript for a Meeting

let sql = r#"
    INSERT INTO transcripts (id, meeting_id, transcript, timestamp)
    VALUES (?1, ?2, ?3, datetime('now'));
"#;
db.execute(sql, (&transcript_id, &meeting_id, &raw_text))?;

Updating the Summary Status

let sql = r#"
    INSERT INTO summary_processes (meeting_id, status, created_at, updated_at)
    VALUES (?1, 'running', datetime('now'), datetime('now'))
    ON CONFLICT(meeting_id) DO UPDATE SET
        status = excluded.status,
        updated_at = datetime('now');
"#;
db.execute(sql, (&meeting_id,))?;

Reading the Configured LLM Provider

let row: (String, String, String) = db.query_row(
    "SELECT provider, model, whisperModel FROM settings WHERE id = 'default'",
    [],
    |r| Ok((r.get(0)?, r.get(1)?, r.get(2)?))
)?;

Summary

  • Meetily uses a single SQLite database with migrations stored under frontend/src-tauri/migrations.
  • The core schema defines six tables: meetings, transcripts, summary_processes, transcript_chunks, settings, and transcript_settings.
  • Foreign keys with ON DELETE CASCADE tie transcripts, chunks, and summary state directly to a meeting record.
  • summary_processes tracks asynchronous LLM jobs, while transcript_chunks stores the segmented text sent to the model.
  • Global and per-transcription settings are separated into the settings and transcript_settings tables.
  • Subsequent migrations add columns such as openRouterApiKey and new tables such as meeting_notes.

Frequently Asked Questions

What is the primary key type used across Meetily's SQLite tables?

Meetily uses TEXT primary keys for all tables, including the meetings and transcripts tables. This design choice keeps identifiers human-readable and avoids integer-sequence complexity in the Tauri backend.

The schema enforces ON DELETE CASCADE on foreign keys in transcripts, summary_processes, and transcript_chunks. When a row in the meetings table is deleted, SQLite automatically removes all linked transcripts, summarisation states, and chunk records.

Where is the Meetily database migration logic implemented?

Migration files are located in frontend/src-tauri/migrations and executed by the Rust database module at frontend/src-tauri/src/database/mod.rs. This module opens the SQLite connection and applies pending migrations when the application starts.

Can different meetings use different LLM providers or API keys?

Yes. While the settings table stores global defaults, the transcript_settings table allows per-transcription overrides. It supports distinct values for provider, model, and provider-specific API keys such as groqApiKey, openaiApiKey, and deepgramApiKey.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →