14 KiB
CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
Note
: When updating this file, also update
.github/copilot-instructions.mdto keep both AI coding assistants in sync.
Overview
Trilium Notes is a hierarchical note-taking application with advanced features like synchronization, scripting, and rich text editing. It's built as a TypeScript monorepo using pnpm, with multiple applications and shared packages.
Development Commands
Setup
pnpm install- Install all dependenciescorepack enable- Enable pnpm if not available
Running Applications
pnpm run server:start- Start development server (http://localhost:8080)pnpm run server:start-prod- Run server in production mode
Building
pnpm run client:build- Build client applicationpnpm run server:build- Build server applicationpnpm run electron:build- Build desktop application
Testing
pnpm test:all- Run all tests (parallel + sequential)pnpm test:parallel- Run tests that can run in parallelpnpm test:sequential- Run tests that must run sequentially (server, ckeditor5-mermaid, ckeditor5-math)pnpm coverage- Generate coverage reports
Architecture Overview
Monorepo Structure
-
apps/: Runnable applications
client/- Frontend application (shared by server and desktop)server/- Node.js server with web interfacedesktop/- Electron desktop applicationweb-clipper/- Browser extension for saving web content- Additional tools:
db-compare,dump-db,edit-docs
-
packages/: Shared libraries
commons/- Shared interfaces and utilitiesckeditor5/- Custom rich text editor with Trilium-specific pluginscodemirror/- Code editor customizationshighlightjs/- Syntax highlighting- Custom CKEditor plugins:
ckeditor5-admonition,ckeditor5-footnotes,ckeditor5-math,ckeditor5-mermaid
Core Architecture Patterns
Three-Layer Cache System
- Becca (Backend Cache): Server-side entity cache (
apps/server/src/becca/) - Froca (Frontend Cache): Client-side mirror of backend data (
apps/client/src/services/froca.ts) - Shaca (Share Cache): Optimized cache for shared/published notes (
apps/server/src/share/)
Entity System
Core entities are defined in apps/server/src/becca/entities/:
BNote- Notes with content and metadataBBranch- Hierarchical relationships between notes (allows multiple parents)BAttribute- Key-value metadata attached to notesBRevision- Note version historyBOption- Application configuration
Widget-Based UI
Frontend uses a widget system (apps/client/src/widgets/):
BasicWidget- Base class for all UI componentsNoteContextAwareWidget- Widgets that respond to note changesRightPanelWidget- Widgets displayed in the right panel- Type-specific widgets in
type_widgets/directory
Reusable Preact Components
Common UI components are available in apps/client/src/widgets/react/ — prefer reusing these over creating custom implementations:
NoItems- Empty state placeholder with icon and message (use for "no results", "too many items", error states)ActionButton- Consistent button styling with icon supportFormTextBox- Text input with validation and controlled input handlingSlider- Range slider with labelCheckbox,RadioButton- Form controlsCollapsibleSection- Expandable content sections
API Architecture
- Internal API: REST endpoints in
apps/server/src/routes/api/ - ETAPI: External API for third-party integrations (
apps/server/src/etapi/) - WebSocket: Real-time synchronization (
apps/server/src/services/ws.ts)
Key Files for Understanding Architecture
-
Application Entry Points:
apps/server/src/main.ts- Server startupapps/client/src/desktop.ts- Client initialization
-
Core Services:
apps/server/src/becca/becca.ts- Backend data managementapps/client/src/services/froca.ts- Frontend data synchronizationapps/server/src/services/backend_script_api.ts- Scripting API
-
Database Schema:
apps/server/src/assets/db/schema.sql- Core database structure
-
Configuration:
package.json- Project dependencies and scripts
Note Types and Features
Trilium supports multiple note types, each with specialized widgets:
- Text: Rich text with CKEditor5 (markdown import/export)
- Code: Syntax-highlighted code editing with CodeMirror
- File: Binary file attachments
- Image: Image display with editing capabilities
- Canvas: Drawing/diagramming with Excalidraw
- Mermaid: Diagram generation
- Relation Map: Visual note relationship mapping
- Web View: Embedded web pages
- Doc/Book: Hierarchical documentation structure
Development Guidelines
Testing Strategy
- Server tests run sequentially due to shared database
- Client tests can run in parallel
- E2E tests use Playwright for both server and desktop apps
- Build validation tests check artifact integrity
- Write concise tests: Group related assertions together in a single test case rather than creating many one-shot tests
- Extract and test business logic: When adding pure business logic (e.g., data transformations, migrations, validations), extract it as a separate function and always write unit tests for it
Scripting System
Trilium provides powerful user scripting capabilities:
- Frontend scripts run in browser context
- Backend scripts run in Node.js context with full API access
- Script API documentation available in
docs/Script API/
Internationalization
- Translation files in
apps/client/src/translations/ - Supported languages: English, German, Spanish, French, Romanian, Chinese
- Only add new translation keys to
en/translation.json— translations for other languages are managed via Weblate and will be contributed by the community - Third-party components (e.g., mind-map context menu) should use i18next
t()for their labels, with the English strings added toen/translation.jsonunder a dedicated namespace (e.g.,"mind-map") - When a translated string contains interpolated components (e.g. links, note references) whose order may vary across languages, use
<Trans>fromreact-i18nextinstead oft(). This lets translators reorder components freely (e.g."<Note/> in <Parent/>"vs"in <Parent/>, <Note/>") - When adding a new locale, follow the step-by-step guide in
docs/Developer Guide/Developer Guide/Concepts/Internationalisation Translations/Adding a new locale.md - Server-side translations (e.g. hidden subtree titles) go in
apps/server/src/assets/translations/en/server.json, not in the clienttranslation.json
Client vs Server Translation Usage
- Client-side:
import { t } from "../services/i18n"with keys inapps/client/src/translations/en/translation.json - Server-side:
import { t } from "i18next"with keys inapps/server/src/assets/translations/en/server.json - Interpolation: Use
{{variable}}for normal interpolation; use{{- variable}}(with hyphen) for unescaped interpolation when the value contains special characters like quotes that shouldn't be HTML-escaped
Electron Desktop App
- Desktop entry point:
apps/desktop/src/main.ts, window management:apps/server/src/services/window.ts - IPC communication: use
electron.ipcMain.on(channel, handler)on server side,electron.ipcRenderer.send(channel, data)on client side - Electron-only features should check
isElectron()fromapps/client/src/services/utils.ts(client) orutils.isElectron(server)
Security Considerations
- Per-note encryption with granular protected sessions
- CSRF protection for API endpoints
- OpenID and TOTP authentication support
- Sanitization of user-generated content
Client-Side API Restrictions
- Do not use
crypto.randomUUID()or other Web Crypto APIs that require secure contexts - Trilium can run over HTTP, not just HTTPS - Use
randomString()fromapps/client/src/services/utils.tsfor generating IDs instead
Storing User Preferences
- Do not use
localStoragefor user preferences — Trilium has a synced options system that persists across devices - To add a new user preference:
- Add the option type to
OptionDefinitionsinpackages/commons/src/lib/options_interface.ts - Add a default value in
apps/server/src/services/options_init.tsin thedefaultOptionsarray - Whitelist the option in
apps/server/src/routes/api/options.tsby adding it toALLOWED_OPTIONS(required for client updates) - Use
useTriliumOption("optionName")hook in React components to read/write the option
- Add the option type to
- Available hooks:
useTriliumOption(string),useTriliumOptionBool,useTriliumOptionInt,useTriliumOptionJson - See
docs/Developer Guide/Developer Guide/Concepts/Options/Creating a new option.mdfor detailed documentation
Shared Types Policy
- Types shared between client and server belong in
@triliumnext/commons(packages/commons/src/lib/) - Import shared types directly from
@triliumnext/commons- do not re-export them from app-specific modules - Keep app-specific types (e.g.,
LlmProviderfor server,StreamCallbacksfor client) in their respective apps
Common Development Tasks
Adding New Note Types
- Create widget in
apps/client/src/widgets/type_widgets/ - Register in
apps/client/src/services/note_types.ts - Add backend handling in
apps/server/src/services/notes.ts
Extending Search
- Search expressions handled in
apps/server/src/services/search/ - Add new search operators in search context files
Custom CKEditor Plugins
- Create new package in
packages/following existing plugin structure - Register in
packages/ckeditor5/src/plugins.ts
Adding Hidden System Notes
The hidden subtree (_hidden) contains system notes with predictable IDs (prefixed with _). Defined in apps/server/src/services/hidden_subtree.ts via the HiddenSubtreeItem interface from @triliumnext/commons.
- Add the note definition to
buildHiddenSubtreeDefinition()inapps/server/src/services/hidden_subtree.ts - Add a translation key for the title in
apps/server/src/assets/translations/en/server.jsonunder"hidden-subtree" - The note is auto-created on startup by
checkHiddenSubtree()— uses deterministic IDs so all sync cluster instances generate the same structure - Key properties:
id(must start with_),title,type,icon(format:bx-icon-namewithoutbxprefix),attributes,children,content - Use
enforceAttributes: trueto keep attributes in sync,enforceBranches: truefor correct placement,enforceDeleted: trueto remove deprecated notes - For launcher bar entries, see
hidden_subtree_launcherbar.ts; for templates, seehidden_subtree_templates.ts
Writing to Notes from Server Services
note.setContent()requires a CLS (Continuation Local Storage) context — wrap calls incls.init(() => { ... })(fromapps/server/src/services/cls.ts)- Operations called from Express routes already have CLS context; standalone services (schedulers, Electron IPC handlers) do not
Adding New LLM Tools
Tools are defined using defineTools() in apps/server/src/services/llm/tools/ and automatically registered for both the LLM chat and MCP server.
- Add the tool definition in the appropriate module (
note_tools.ts,attribute_tools.ts,attachment_tools.ts,hierarchy_tools.ts) or create a new module - Each tool needs:
description,inputSchema(Zod),executefunction, and optionallymutates: truefor write operations - If creating a new module, wrap tools in
defineTools({...})and add the registry toallToolRegistriesintools/index.ts - Add a client-side friendly name in
apps/client/src/translations/en/translation.jsonunderllm.tools.<tool_name>— use imperative tense (e.g. "Search notes", "Create note", "Get attributes"), not present continuous - Use ETAPI (
apps/server/src/etapi/) as inspiration for what fields to expose, but do not import ETAPI mappers — inline the field mappings directly in the tool so the LLM layer stays decoupled from the API layer
Updating PDF.js
- Update
pdfjs-distversion inpackages/pdfjs-viewer/package.json - Run
npx tsx scripts/update-viewer.tsfrom that directory - Run
pnpm buildto verify success - Commit all changes including updated viewer files
Database Migrations
- Add migration scripts in
apps/server/src/migrations/ - Update schema in
apps/server/src/assets/db/schema.sql
Server-Side Static Assets
- Static assets (templates, SQL, translations, etc.) go in
apps/server/src/assets/ - Access them at runtime via
RESOURCE_DIRfromapps/server/src/services/resource_dir.ts(e.g.path.join(RESOURCE_DIR, "llm", "skills", "file.md")) - Do not use
import.meta.url/fileURLToPathto resolve file paths — the server is bundled into CJS for production, soimport.meta.urlwill not point to the source directory - Do not use
__dirnamewith relative paths from source files — after bundling,__dirnamepoints to the bundle output, not the original source tree
MCP Server
- Trilium exposes an MCP (Model Context Protocol) server at
http://localhost:8080/mcp, configured in.mcp.json - The MCP server is only available when the Trilium server is running (
pnpm run server:start) - It provides tools for reading, searching, and modifying notes directly from the AI assistant
- Use it to interact with actual note data when developing or debugging note-related features
Build System Notes
- Uses pnpm for monorepo management
- Vite for fast development builds
- ESBuild for production optimization
- pnpm workspaces for dependency management
- Docker support with multi-stage builds