Monorepo Overview
NeoTavern is a local-first application: a single Fastify process serves the API and the optional built frontend, with no external databases, queues, or containers required.
Monorepo Layout
The workspace is a pnpm monorepo with two top-level groups, apps/ and
packages/:
apps/
server/ # Fastify backend: API, prompt pipeline, SSE, legacy host
web/ # React SPA
plugin-runtime/ # Restricted Node.js process for backend plugins
desktop/ # Tauri 2 shell; runs the server as a sidecar process
packages/
shared/ # UUIDv7 IDs, Result, errors, logger, async utilities
contracts/ # TypeBox API schemas — single source of truth
db/ # SQLite: schema, migrations, repositories, FTS5
ui/ # Headless components on Radix primitives
i18n/ # i18next setup and language resources
plugin-sdk/ # Plugin manifest, permissions, and API contracts
theme-sdk/ # Theme tokens, levels, and inheritance
provider-sdk/ # Provider adapter contract and adapters
legacy-compat/ # window globals and DOM compatibility islands
gestures/ # Framework-agnostic row gestures
plugin-build/ # Plugin build and publish pipeline
Apps
apps/server— the Fastify backend. It exposes the/api/v2/*API, runs the prompt pipeline, streams generation over SSE, and hosts the Express-compatible legacy surface. Each module is an isolated Fastify plugin.apps/web— the React SPA. It talks to the server over HTTP and renders the chat workspace, plus the surfaces for characters, settings, providers, themes, and plugins.apps/plugin-runtime— a permission-limited Node.js process in which untrusted backend plugins execute, isolated from the main server process.apps/desktop— the Tauri 2 shell. It launches the compiled server as a self-contained Node.js sidecar and opens the webview only after the local API is ready.
Packages
Shared code lives in narrowly scoped packages under packages/. Every
package has one responsibility, and dependencies only point downward:
server and web depend on packages, and packages depend at most on
shared and contracts. See Packages for the full breakdown.
Data Flow
A typical request flows through these layers:
- The frontend calls an
/api/v2/*endpoint through TanStack Query. - Fastify validates the input against a TypeBox schema and returns errors
in the
{ code, params, traceId }envelope. - Repositories in
@neotavern/dbread and write SQLite, with cursor pagination and FTS5 search. - Generation runs
POST /api/v2/chats/:id/generate: the prompt pipeline assembles the context, the provider adapter serializes the request, the response streams back over SSE, and the message is saved.
The web app is a single page: routes change the chat workspace, while characters, settings, providers, themes, and plugins render in a dialog surface over the preserved chat location.
Local-First Principle
Everything runs on your machine:
- The backend binds to
127.0.0.1by default. Remote access is an explicit opt-in with bounded sessions and HTTPS requirements. - All data lives in one local data directory: a single SQLite database plus a content-addressed file store. No PostgreSQL, Redis, or Docker.
- The app works offline. Provider calls are the only network traffic, and
the built-in
echoadapter lets you test the whole pipeline without any provider. - Backups, exports, and the SillyTavern import all happen locally through the same SQLite and file APIs.
See Data & Storage for the storage layer and Prompt Pipeline for the generation path.