Joplin is a free, open-source note-taking and to-do application with end-to-end encryption, synchronization across devices, and an extensible plugin system. This guide covers the full architecture, configuration, deployment, security posture, and developer internals of the Joplin ecosystem — a monorepo spanning 20+ packages that produces Desktop (Electron), Mobile (React Native), CLI (Node.js), Server (Node.js + PostgreSQL), and Web Clipper (browser extension) applications.
For everyday usage, installation walkthroughs, and feature guides, see the User Guide.
1. Architecture Overview
1.1 High-Level System Topology
┌─────────────────────────────────────────────────────────────────────────┐
│ Joplin Ecosystem │
│ │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ ┌────────────┐ │
│ │ app-desktop │ │ app-mobile │ │ app-cli │ │ app-clipper│ │
│ │ (Electron) │ │(React Native) │ │ (Node.js) │ │ (Browser) │ │
│ └──────┬───────┘ └──────┬───────┘ └──────┬───────┘ └─────┬──────┘ │
│ │ │ │ │ │
│ └────────┬────────┴────────┬────────┘ │ │
│ │ │ │ │
│ ┌──────▼───────┐ ┌─────▼──────┐ ┌──────▼───────┐ │
│ │ lib │ │ renderer │ │ REST API │ │
│ │(Models/Sync/ │ │ (Markdown) │ │ (localhost) │ │
│ │ Services) │ │ │ │ │ │
│ └──────┬───────┘ └────────────┘ └──────────────┘ │
│ │ │
│ ┌────────▼────────────────────────────────────────────────┐ │
│ │ Sync Layer (SyncTargetRegistry) │ │
│ │ │ │
│ │ ┌─────────┐ ┌──────────┐ ┌────────┐ ┌──────────────┐ │ │
│ │ │Joplin │ │Nextcloud/│ │OneDrive│ │ S3/Dropbox │ │ │
│ │ │Cloud/ │ │WebDAV │ │ │ │ /Filesystem │ │ │
│ │ │Server │ │ │ │ │ │ │ │ │
│ │ └────┬────┘ └──────────┘ └────────┘ └──────────────┘ │ │
│ └───────┼────────────────────────────────────────────────┘ │
│ │ │
│ ┌───────▼────────┐ │
│ │ Joplin Server │ │
│ │ (Koa + PG) │ │
│ └───────┬────────┘ │
│ │ │
│ ┌───────▼────────┐ │
│ │ PostgreSQL │ │
│ └────────────────┘ │
└─────────────────────────────────────────────────────────────────────────┘
Note
Joplin's architecture is a monorepo with clear package boundaries. The
libpackage is the gravitational center — every client application depends on it for models, synchronization logic, encryption, and service management. The server is an independent deployment that shares data format conventions but runs its own Koa-based stack with PostgreSQL.
1.2 Monorepo Package Inventory
The repository uses Yarn 4.9.2 workspaces to manage 25+ packages under the packages/ directory:
| Package | Role | Technology | Runtime |
|---|---|---|---|
app-desktop |
Desktop application | Electron, TypeScript | Node.js + Chromium |
app-mobile |
Mobile application | React Native, TypeScript | iOS / Android |
app-cli |
CLI application | Node.js, terminal-kit TUI | Node.js |
app-clipper |
Web clipper extension | React | Browser (Chrome/Firefox) |
server |
Joplin Server | Node.js, Koa, PostgreSQL | Node.js |
lib |
Shared library | TypeScript | All platforms |
renderer |
Markdown renderer | TypeScript | All platforms |
editor |
Editor components | CodeMirror 6, ProseMirror, TinyMCE | Desktop + Mobile |
onenote-converter |
OneNote import | Rust compiled to WASM | Desktop + CLI |
transcribe |
Transcription service | Node.js | Server |
whisper-voice-typing |
Voice typing | C++ (Whisper), Android NDK | Mobile (Android) |
utils |
Shared utilities | TypeScript | All platforms |
fork-htmlparser2 |
HTML parser fork | TypeScript | All platforms |
fork-sax |
SAX parser fork | JavaScript | All platforms |
fork-uslug |
URL slug fork | JavaScript | All platforms |
turndown |
HTML-to-Markdown converter | TypeScript | All platforms |
turndown-plugin-gfm |
GFM plugin for turndown | TypeScript | All platforms |
pdf-viewer |
PDF viewing component | React | Desktop |
htmlpack |
HTML packing utility | TypeScript | Build-time |
plugin-repo-cli |
Plugin repository management | TypeScript | CLI |
default-plugins |
Default bundled plugins | TypeScript | Desktop |
generator-joplin |
Yeoman plugin generator | JavaScript | CLI |
doc-builder |
Documentation site | Docusaurus | Build-time |
tools |
Build and dev tools | TypeScript | Build-time |
plugins/ToggleSidebars |
Built-in sidebar toggle plugin | TypeScript | Desktop |
1.3 Data Flow: Note Lifecycle
User creates/edits note
│
▼
┌─────────────────┐
│ Editor Layer │ CodeMirror 6 (Markdown) / TinyMCE (WYSIWYG)
│ (app-desktop/ │ ProseMirror (mobile WYSIWYG)
│ app-mobile) │
└────────┬────────┘
│
▼
┌─────────────────┐
│ BaseModel │ Note model validates, assigns metadata
│ (lib) │ (id, parent_id, timestamps, markup_language)
└────────┬────────┘
│
▼
┌─────────────────┐
│ SQLite DB │ Local persistence (WAL mode, FK enforcement)
│ (client) │ Schema migrations managed per version
└────────┬────────┘
│
▼ (on sync trigger)
┌─────────────────┐
│ Synchronizer │ Delta-based sync, lock conflict resolution
│ (lib) │ Attachment download mode: always/manual/auto
└────────┬────────┘
│
▼
┌─────────────────┐
│ Sync Target │ Joplin Server / Cloud / Nextcloud / WebDAV /
│ (lib) │ OneDrive / Dropbox / S3 / Filesystem
└────────┬────────┘
│
▼
┌─────────────────┐
│ Remote Storage │ Server-side PostgreSQL or file-based target
└─────────────────┘
1.4 Sync Target Registry
All synchronization backends are registered through SyncTargetRegistry with numeric identifiers:
| ID | Target | Protocol | Notes |
|---|---|---|---|
| 1 | Memory | In-process | Testing only |
| 2 | Filesystem | Local FS | No network required |
| 3 | OneDrive (personal) | REST API | Microsoft Graph API |
| 4 | OneDrive (business) | REST API | Microsoft Graph API |
| 5 | Nextcloud | WebDAV | Nextcloud-specific handling |
| 6 | WebDAV | WebDAV | Generic WebDAV servers |
| 7 | Dropbox | REST API | Dropbox API v2 |
| 8 | Amazon S3 | S3 protocol | Any S3-compatible endpoint |
| 9 | Joplin Server | REST API | Self-hosted server |
| 10 | Joplin Cloud | REST API | Managed hosting |
| 11 | Joplin Server (SAML) | REST API + SAML | Enterprise SSO |
Max concurrent sync connections: configurable 1–20, default 5.
1.5 Client-Side Application Architecture
Each client application follows a common pattern rooted in BaseApplication:
BaseApplication
├── Application (Desktop) ─── extends ──→ Bridge (Electron IPC)
├── Application (CLI) ──────── extends ──→ AppGui (terminal-kit)
└── AppComponent (Mobile) ── React Native root component
Key shared services instantiated by all clients:
- Setting — centralized configuration (JSON file or SQLite-backed)
- SyncTargetRegistry — sync backend selection
- DecryptionWorker — background E2EE decryption
- SearchEngine — full-text search (FTS4/FTS5 on SQLite)
- AlarmService — to-do reminder scheduling
- PluginService — plugin lifecycle management (desktop only)
- ResourceEditWatcher — external editor integration
- ExternalEditWatcher — file system monitoring for note editing
- EventManager — pub/sub event bus
- Logger — structured logging with configurable levels
2. System Requirements
2.1 Joplin Server
| Component | Minimum | Recommended |
|---|---|---|
| Node.js | 18.x | 20.x LTS or newer |
| PostgreSQL | Version enforced at startup (see error messages) | Latest stable |
| RAM | 512 MB | 2 GB+ for production |
| Disk | 10 GB | Scales with attachment storage |
| OS | Linux (any modern distro) | Ubuntu 22.04+ / Debian 12+ |
2.2 Desktop Application (Development)
| Component | Requirement |
|---|---|
| Node.js | >= 18 |
| Yarn | 4.9.2 (workspace-enabled) |
| Electron | Managed by package.json |
| OS | Windows 10+, macOS 11+, Linux (glibc 2.28+) |
| Build tools | C++ compiler (node-gyp deps), Python 3 |
2.3 Mobile Application (Development)
| Component | Requirement |
|---|---|
| React Native CLI | Per project configuration |
| Android SDK | API 24+ (Android 7.0+) |
| Xcode | 14+ (for iOS) |
| CocoaPods | Latest (iOS) |
| NDK | Required for whisper-voice-typing (C++) |
2.4 CLI Application
| Component | Requirement |
|---|---|
| Node.js | >= 18 |
| npm | Via npm global install |
| terminal-kit | Bundled dependency |
2.5 OneNote Converter
| Component | Requirement |
|---|---|
| Rust toolchain | For building from source |
| wasm-pack | For WASM compilation |
| Node.js | >= 18 (for runtime loading) |
2.6 Build System Requirements
The full monorepo build requires:
# Install dependencies (Yarn 4.9.2 workspaces)
yarn install
# Build all packages (order-aware via workspace dependencies)
yarn build
# Individual package builds use Gulp tasks internally
Key build tooling:
- Yarn 4.9.2 — workspace management, dependency resolution
- Gulp — task orchestration within packages
- Electron Builder — desktop packaging (Windows/macOS/Linux installers)
- React Native CLI — mobile builds (iOS/Android)
- Rust/wasm-pack — OneNote converter WASM compilation
- Playwright — integration testing framework
3. Configuration Reference
3.1 Settings Architecture
Joplin uses a centralized Setting class that manages 100+ configuration options. Settings are persisted via two storage backends:
| Storage Type | Mechanism | Used By |
|---|---|---|
| File | JSON file (settings.json or equivalent) |
Desktop, CLI |
| Database | SQLite table | Mobile, fallback |
Settings are organized into sections:
| Section | Scope |
|---|---|
sync |
Synchronization target, credentials, intervals |
appearance |
Theme, font, layout |
note |
Default note properties, markup language |
editor |
Editor behavior, keyboard shortcuts |
folder |
Notebook display, sorting |
Settings have per-app-type visibility flags controlling which settings appear in Desktop, Mobile, or CLI.
Important
Secure settings (sync passwords, encryption passphrases) are stored via the operating system's native keychain (macOS Keychain, Windows Credential Manager, Linux Secret Service). These values are never written to the JSON settings file in plaintext. If the keychain is unavailable, Joplin falls back to database storage with a warning.
3.2 Server Environment Variables
The Joplin Server accepts the following environment variables:
| Variable | Type | Description |
|---|---|---|
env |
string | Running environment (production, development, testing) |
stack-trace |
boolean | Enable detailed stack traces in error responses |
db-config-filename |
string | Path to database configuration file |
MAX_TIME_DRIFT |
integer (ms) | Maximum allowed clock drift between client and server. Set to 0 to disable drift checking entirely. |
3.3 Database Configuration
The server reads database configuration from a file specified by db-config-filename. The PostgreSQL connection parameters include:
- Host and port
- Database name
- Username and password
- SSL configuration
- Connection pool settings
- Connection timeout (triggers "Timeout trying to connect to database" on failure)
3.4 Client Settings — Complete Reference
Below is the complete catalog of built-in settings, organized by section. Each setting includes its type, default value (where deterministic), and the app types where it is visible.
3.4.1 Sync Settings
| Setting | Type | Default | Description |
|---|---|---|---|
sync.target |
integer | 0 (none) | Active sync target ID (see Sync Target Registry in Section 1.4) |
sync.interval |
integer | 300 | Sync interval in seconds |
sync.maxConcurrentConnections |
integer | 5 | Maximum parallel sync connections (range: 1–20) |
sync.resourceDownloadMode |
string | always |
Attachment download behavior: always, manual, or auto |
sync.wipeOutFailSafe |
boolean | true | Prevents sync from deleting all data if remote appears empty |
sync.*.path |
string | — | Target-specific path (WebDAV URL, S3 bucket, filesystem path) |
sync.*.username |
string | — | Target-specific username |
sync.*.password |
string (secure) | — | Target-specific password (keychain-stored) |
3.4.2 Appearance Settings
| Setting | Type | Default | Description |
|---|---|---|---|
theme |
integer | 1 | Theme ID (light, dark, OLED dark, Solarized, Dracula, Nord) |
themeAutoDetect |
boolean | false | Follow OS dark/light preference |
layoutButtonSequence |
integer | 0 | Editor/viewer layout button ordering |
showNoteCounts |
boolean | true | Display note counts in notebook sidebar |
uncompletedTodosOnTop |
boolean | true | Sort incomplete to-dos above completed |
noteListRenderingType |
integer | — | List view rendering style |
3.4.3 Note Settings
| Setting | Type | Default | Description |
|---|---|---|---|
notes.sortOrder.field |
string | user_updated_time |
Field used for note sorting |
notes.sortOrder.reverse |
boolean | true | Reverse sort direction (newest first) |
newTodoFocus |
string | title |
Focus target when creating a new to-do |
newNoteFocus |
string | body |
Focus target when creating a new note |
trackLocation |
boolean | true | Attach geolocation to notes (mobile) |
3.4.4 Editor Settings
| Setting | Type | Default | Description |
|---|---|---|---|
editor |
string | — | External editor command (desktop) |
editor.codeView |
boolean | true | Use code (Markdown) view by default |
editor.spellcheckBeta |
boolean | false | Enable spellcheck in editor |
editor.autoMatchingBraces |
boolean | true | Auto-close brackets and braces |
editor.keyboardMode |
string | default |
Keyboard mode: default, emacs, vim |
markdown.plugin.* |
boolean | varies | Toggle individual markdown rendering plugins (katex, mhchem, mark, footnote, toc, sub, sup, deflist, abbr, emoji, insert, multitable, softbreaks) |
3.4.5 Encryption Settings
| Setting | Type | Default | Description |
|---|---|---|---|
encryption.enabled |
boolean | false | Enable E2EE |
encryption.activeMasterKeyId |
string | — | Active master key for encryption |
encryption.passwordCache |
object | {} | Cached master key passwords (keychain) |
encryption.shouldReencrypt |
integer | 0 | Pending re-encryption flag |
encryption.masterPassword |
string (secure) | — | Master encryption password |
3.4.6 Plugin Settings
| Setting | Type | Default | Description |
|---|---|---|---|
plugins.states |
object | {} | Plugin enabled/disabled states |
plugins.devPluginPaths |
string | — | Comma-separated paths to development plugins |
3.4.7 Advanced Settings
| Setting | Type | Default | Description |
|---|---|---|---|
net.customCertificates |
string | — | Path(s) to custom CA certificates |
net.ignoreTlsErrors |
boolean | false | Ignore TLS certificate errors |
net.proxyEnabled |
boolean | false | Enable proxy for network requests |
net.proxyUrl |
string | — | Proxy server URL |
api.port |
integer | 41184 | Local REST API port (clipper/plugin access) |
api.token |
string | — | REST API authentication token |
locale |
string | system | UI language |
dateFormat |
string | DD/MM/YYYY |
Date display format |
timeFormat |
string | HH:mm |
Time display format |
showTrayIcon |
boolean | true | Show system tray icon (desktop) |
startMinimized |
boolean | false | Start minimized to tray |
collapsedFolderIds |
array | [] | IDs of collapsed notebooks |
db.ftsEnabled |
integer | -1 | Full-text search engine state |
clipperServer.autoStart |
boolean | false | Auto-start clipper server |
revisionService.enabled |
boolean | true | Enable note history/revisions |
revisionService.ttlDays |
integer | 90 | Days to retain note revisions |
3.5 Profile Configuration
Joplin supports multiple profiles via ProfileConfig:
- Each profile has its own settings file, database, and sync target
- Profile switching is available on desktop
- Profile data is isolated — notes, notebooks, and resources are per-profile
- The default profile is profile 0
4. Deployment & Scaling
4.1 Joplin Server Deployment
4.1.1 Docker Deployment (Recommended)
The standard deployment uses Docker with PostgreSQL:
# docker-compose.yml
version: '3'
services:
db:
image: postgres:16
volumes:
- ./data/postgres:/var/lib/postgresql/data
environment:
- POSTGRES_PASSWORD=<strong_password>
- POSTGRES_USER=joplin
- POSTGRES_DB=joplin
restart: unless-stopped
app:
image: joplin/server:latest
depends_on:
- db
ports:
- "22300:22300"
environment:
- APP_PORT=22300
- APP_BASE_URL=https://joplin.yourdomain.com
- DB_CLIENT=pg
- POSTGRES_PASSWORD=<strong_password>
- POSTGRES_DATABASE=joplin
- POSTGRES_USER=joplin
- POSTGRES_PORT=5432
- POSTGRES_HOST=db
restart: unless-stopped
4.1.2 Reverse Proxy Configuration
For production, place the server behind a reverse proxy (Nginx, Traefik, Caddy):
# Nginx example
server {
listen 443 ssl;
server_name joplin.yourdomain.com;
ssl_certificate /path/to/cert.pem;
ssl_certificate_key /path/to/key.pem;
client_max_body_size 200M; # For large attachments
location / {
proxy_pass http://127.0.0.1:22300;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
4.1.3 Bare Metal Deployment
# Clone and build
git clone https://github.com/laurent22/joplin.git
cd joplin
yarn install
cd packages/server
yarn build
# Configure database
export DB_CLIENT=pg
export POSTGRES_HOST=localhost
export POSTGRES_PORT=5432
export POSTGRES_DATABASE=joplin
export POSTGRES_USER=joplin
export POSTGRES_PASSWORD=<password>
export APP_BASE_URL=https://joplin.yourdomain.com
# Run migrations and start
yarn start
4.2 Database Migrations
The server manages schema migrations automatically on startup. If migrations are pending, the server logs:
One or more migrations need to be applied
Migrations run in sequence. The server will not start serving requests until all migrations complete. For large databases, plan for downtime during major version upgrades.
4.3 Scaling Considerations
4.3.1 Vertical Scaling
| Users | RAM | CPU | PostgreSQL |
|---|---|---|---|
| 1–50 | 1 GB | 1 core | Shared or small instance |
| 50–500 | 2–4 GB | 2 cores | Dedicated, 2 GB RAM |
| 500–5000 | 8 GB+ | 4+ cores | Dedicated, 8 GB+ RAM, SSD |
4.3.2 Storage Planning
Storage grows with:
- Note content (typically small, < 1 KB per note)
- Attachments/resources (images, PDFs — can be large)
- Note revisions (90-day retention by default)
- Server-side item versions for delta sync
Formula: base_storage = num_users * avg_attachments_per_user * avg_attachment_size * 1.2 (overhead)
4.3.3 Connection Pooling
PostgreSQL connections are managed via a connection pool in the server. The TransactionHandler manages transaction lifecycle, ensuring proper isolation and cleanup. Configure PostgreSQL's max_connections based on expected concurrency — each active sync session can consume 1–3 connections.
4.4 Desktop Distribution
Electron Builder produces platform-specific packages:
| Platform | Format |
|---|---|
| Windows | NSIS installer (.exe), portable (.exe) |
| macOS | DMG, App Store |
| Linux | AppImage, Snap, Flatpak, DEB, RPM |
4.5 Mobile Distribution
| Platform | Store | Build System |
|---|---|---|
| Android | Google Play, F-Droid | React Native + Gradle |
| iOS | App Store | React Native + Xcode |
5. Operations
5.1 Server Administration
5.1.1 User Management
The server exposes user management through its API and admin interface:
POST /api/users Create user
GET /api/users List users
GET /api/users/:id Get user details
PATCH /api/users/:id Update user
Note
The first user created on a fresh Joplin Server instance is automatically an admin. Subsequent users are standard users by default. Admin privileges can be granted via the PATCH endpoint or the web admin interface.
User accounts can be disabled, which triggers the error "This account is disabled" on any subsequent authentication attempt.
5.1.2 Session Management
Sessions are managed through the /api/sessions endpoint:
POST /api/sessions Create session (login)
Sessions have configurable expiration. Expired sessions trigger: "Your session has expired."
5.1.3 Health Monitoring
GET /api/ping Basic health check (no auth required)
GET /api/debug Debug information (admin only)
Tip
Use
/api/pingfor load balancer health checks and container orchestrator liveness probes. It returns a simple response without touching the database. Use/api/debugfor deeper diagnostics, but protect it behind authentication as it may expose internal state.
5.1.4 Batch Operations
The server supports batch operations to reduce round-trips during sync:
POST /api/batch Execute multiple operations atomically
POST /api/batch_items Batch item operations
These endpoints are used internally by the sync engine. Direct use requires understanding the item serialization format.
5.2 Backup Strategies
5.2.1 Server Backup
# PostgreSQL dump
pg_dump -U joplin -d joplin -F c -f joplin_backup_$(date +%Y%m%d).dump
# Restore
pg_restore -U joplin -d joplin -c joplin_backup_20260507.dump
Back up both the database and the attachment storage directory. The database contains item metadata and content; attachments are stored as binary blobs or on the filesystem depending on configuration.
5.2.2 Client Backup
Client data lives in the local SQLite database and a resources directory:
| Platform | Default Location |
|---|---|
| Windows | %APPDATA%\joplin-desktop\ |
| macOS | ~/.config/joplin-desktop/ |
| Linux | ~/.config/joplin-desktop/ |
| CLI | ~/.config/joplin/ |
Back up: database.sqlite, resources/, settings.json, and keymap-desktop.json.
5.3 Log Management
Joplin uses a structured Logger class with configurable levels:
| Level | When to Use |
|---|---|
error |
Unrecoverable failures |
warn |
Degraded operation, recoverable issues |
info |
Normal operational events |
debug |
Detailed diagnostic information |
Desktop logs: accessible via Help -> Open Log Directory.
Server logs: standard output (capture via Docker logging driver or systemd journal).
5.4 Monitoring Checklist
| Check | Frequency | Method |
|---|---|---|
| Server responsiveness | Every 30s | GET /api/ping |
| Database connectivity | Every 60s | Monitor server logs for connection timeouts |
| Disk space | Hourly | OS-level monitoring |
| Sync errors | Daily | Review server logs for error patterns |
| Migration status | After upgrades | Check startup logs |
| SSL certificate | Weekly | External monitoring |
| PostgreSQL connections | Continuous | pg_stat_activity |
| Memory usage | Continuous | Container/process metrics |
Note
The
PerformanceTimerclass in the server provides internal timing metrics for request processing. Enable debug logging to capture per-route latency data for performance analysis.
6. Performance
6.1 Client-Side Performance
6.1.1 SQLite Optimization
Joplin clients use SQLite with the following performance-critical settings:
- WAL mode — Write-Ahead Logging enables concurrent reads during writes, essential for smooth UI during sync
- Foreign key enforcement — enabled at connection time (
PRAGMA foreign_keys = ON) - Full-text search — FTS4/FTS5 extensions for note search, configurable via
db.ftsEnabled
6.1.2 Search Engine
The SearchEngine service maintains a full-text index:
- Index rebuild runs on first launch or after profile import
- Incremental indexing on note save
- Supports quoted phrases, AND/OR operators, tag filters
- FTS5 preferred where available (better ranking, prefix queries)
6.1.3 Resource Management
Attachments (resources) are the primary driver of storage and bandwidth:
| Download Mode | Behavior | Best For |
|---|---|---|
always |
Downloads all attachments during sync | Fast connections, full offline access |
manual |
Downloads only when user requests | Limited bandwidth/storage |
auto |
Downloads based on usage patterns | Balanced approach |
6.2 Server-Side Performance
6.2.1 Delta Sync
The server implements delta-based synchronization via the items API:
GET /api/items/:id/delta Retrieve changes since cursor
Delta sync minimizes data transfer by sending only changed items since the client's last sync cursor. A client with 10,000 notes only transfers the handful that changed since the last sync.
6.2.2 Rate Limiting
The server implements rate limiting middleware to prevent abuse:
- Returns
ErrorTooManyRequests(HTTP 429) when limits are exceeded - Per-user or per-IP rate limiting
- Configurable thresholds
6.2.3 Caching
The server uses a Cache class for frequently accessed data:
- In-memory cache with TTL-based expiration
- Reduces database queries for hot paths (user sessions, item metadata)
- Cache invalidation on write operations
6.2.4 Payload Limits
Large attachments trigger ErrorPayloadTooLarge (HTTP 413). Configure:
- Reverse proxy:
client_max_body_size(Nginx) or equivalent - Server-side: payload size limits in server configuration
- Client-side: resource upload chunking for large files
6.3 Performance Tuning Checklist
| Area | Tuning |
|---|---|
| Sync interval | Increase from 300s to 600s+ for large installations |
| Max concurrent connections | Reduce from 5 to 2–3 on slow networks |
| Resource download mode | Use manual or auto for mobile/limited bandwidth |
| PostgreSQL | Enable shared_buffers = 25% RAM, effective_cache_size = 75% RAM |
| PostgreSQL | Set work_mem = 16MB+ for complex queries |
| Revision retention | Reduce revisionService.ttlDays from 90 to 30 if storage-constrained |
| FTS rebuild | Schedule during low-usage periods after bulk imports |
7. Security
7.1 End-to-End Encryption (E2EE)
Joplin implements E2EE with a master key architecture:
┌─────────────────────────────────────────────────────┐
│ E2EE Architecture │
│ │
│ Master Password ──→ Master Key (encrypted) │
│ │ │
│ ▼ │
│ Decrypt Master Key │
│ │ │
│ ▼ │
│ Per-Item Encryption │
│ (AES-256-CBC) │
│ │ │
│ ┌────────┴────────┐ │
│ ▼ ▼ │
│ Note Content Resource Data │
│ (encrypted) (encrypted) │
│ │
│ Public-Private Keys (PPK): │
│ ├── WebCrypto RSA key pairs │
│ ├── Used for sharing encrypted notebooks │
│ └── Per-user key pair generation │
│ │
│ DecryptionWorker: │
│ ├── Background service │
│ ├── Processes encrypted items in batches │
│ └── Checks per-item encryption status │
└─────────────────────────────────────────────────────┘
Warning
Losing the master password means permanent data loss for encrypted notes. There is no recovery mechanism by design — the server never has access to decryption keys. Ensure users understand this before enabling E2EE. Store the master password in a secure password manager.
Key implementation details:
- Master key rotation: supported but requires re-encryption of all items (triggered via
encryption.shouldReencrypt) - Per-item check: each item carries its own encryption flag, enabling gradual encryption/decryption
- PPK (Public-Private Keys): WebCrypto-based RSA key pairs enable notebook sharing between users with E2EE active
- DecryptionWorker: runs as a background service, processing encrypted items without blocking the UI
7.2 Server Authentication
7.2.1 Session-Based Auth
Standard authentication flow:
- Client sends credentials to
POST /api/sessions - Server validates and returns session token
- Client includes token in subsequent requests
- Invalid/expired tokens yield "Invalid or missing session" or "Your session has expired"
7.2.2 SAML Authentication
Enterprise SSO via SAML (sync target ID 11):
GET /api/saml SAML metadata / initiate flow
POST /api/saml SAML assertion consumer
GET /api/login_with_code/:id Complete SAML login
SAML must be explicitly enabled in server configuration. When disabled, requests to SAML endpoints return: "SAML support is disabled."
7.2.3 Application Auth
GET/POST /api/application_auth Application-level authentication
Used for OAuth-style application authorization flows.
7.3 CSRF Protection
The server implements CSRF protection middleware:
- CSRF tokens are required for state-changing requests
- Missing tokens trigger: "CSRF token is missing"
- Invalid tokens trigger: "Invalid CSRF token"
- Tokens are validated per-request
Caution
API integrations that bypass the web interface must handle CSRF tokens correctly. Obtain a token from the login response and include it in all subsequent POST/PATCH/DELETE requests. Failure to do so results in 403 Forbidden responses.
7.4 Client REST API Security
The local REST API (port 41184 by default) is secured via token authentication:
GET /api/ping No auth required
GET /api/notes Requires api.token
POST /api/notes Requires api.token
GET /api/folders Requires api.token
...
The REST API listens on localhost only. The api.token setting controls access. The Web Clipper and plugins use this API.
Error classes for the client REST API:
| Error Class | HTTP Status | Description |
|---|---|---|
ErrorBadRequest |
400 | Malformed request |
ErrorForbidden |
403 | Authentication failure |
ErrorNotFound |
404 | Resource not found |
ErrorMethodNotAllowed |
405 | Unsupported HTTP method |
7.5 Server Error Hierarchy
The server uses a comprehensive ApiError hierarchy for consistent error handling:
| Error Class | HTTP Status | Meaning |
|---|---|---|
ErrorBadRequest |
400 | Malformed request parameters |
ErrorForbidden |
403 | Insufficient permissions |
ErrorNotFound |
404 | Resource does not exist |
ErrorMethodNotAllowed |
405 | HTTP method not supported for route |
ErrorConflict |
409 | Resource conflict (concurrent modification) |
ErrorPreconditionFailed |
412 | Precondition header mismatch |
ErrorPayloadTooLarge |
413 | Request body exceeds size limit |
ErrorUnprocessableEntity |
422 | Valid syntax but semantic error |
ErrorTooManyRequests |
429 | Rate limit exceeded |
ErrorNotImplemented |
501 | Feature not available |
ErrorBadGateway |
502 | Upstream service failure |
ErrorServiceUnavailable |
503 | Server temporarily unavailable |
ErrorResyncRequired |
409 (custom) | Client must perform full resync |
7.6 Network Security
| Concern | Mitigation |
|---|---|
| TLS | All sync targets support HTTPS; custom CA certs via net.customCertificates |
| TLS bypass | net.ignoreTlsErrors exists but should never be used in production |
| Proxy | net.proxyEnabled + net.proxyUrl for corporate environments |
| Clock sync | MAX_TIME_DRIFT prevents replay attacks; error: "The device time drift is ${timeDrift}ms" |
| Localhost API | REST API binds to 127.0.0.1 only |
7.7 Data Protection
| Feature | Implementation |
|---|---|
| E2EE | AES-256-CBC per-item, RSA key exchange for sharing |
| Secure storage | OS keychain for passwords and tokens |
| Local DB | SQLite with file permissions (no encryption at rest by default) |
| Note revisions | 90-day history for accidental deletion recovery |
| Trash | Soft-delete with configurable retention |
| Sync wipe-out protection | sync.wipeOutFailSafe prevents accidental mass deletion |
7.8 Plugin Security
Warning
Plugins execute within a sandbox but have access to note data through the JoplinData API. Only install plugins from trusted sources. The plugin system validates manifest fields (ID: 16-256 characters, version: semver pattern) but cannot guarantee code safety. Review plugin permissions before installation.
Plugin security model:
- Sandbox execution: plugins run in isolated contexts via
executeSandboxCall - API surface control: plugins access data through
JoplinData(CRUD) andJoplinImaging(image manipulation) - Platform compatibility: plugins declare supported platforms; incompatible plugins are rejected
- Content scripts: loaded with plugin/default export pattern, restricted to declared capabilities
8. Developer Internals
8.1 Core Data Model
The data model is rooted in BaseModel with type-specific subclasses:
BaseModel
├── BaseItem (sync-capable items)
│ ├── Note (type: 1)
│ ├── Folder (type: 2) [Notebook]
│ ├── Resource (type: 4) [Attachment]
│ ├── Tag (type: 5)
│ ├── NoteTag (type: 6) [Join table]
│ ├── MasterKey (type: 9)
│ └── Revision (type: 13)
└── Setting (singleton, not synced as item)
Key model properties shared across types:
| Field | Type | Description |
|---|---|---|
id |
string (UUID) | Primary identifier |
title |
string | Display name |
created_time |
integer (epoch ms) | Creation timestamp |
updated_time |
integer (epoch ms) | Last modification |
user_created_time |
integer (epoch ms) | User-facing creation time |
user_updated_time |
integer (epoch ms) | User-facing modification time |
encryption_cipher_text |
string | Encrypted content (when E2EE active) |
encryption_applied |
integer (0/1) | Encryption flag |
is_shared |
integer (0/1) | Sharing flag |
8.2 Server Model Layer
The server uses a Models factory class to instantiate model objects:
// Models factory provides typed access to all model classes
const models = new Models(db, config);
const user = await models.user().load(userId);
const items = await models.item().loadByIds(itemIds);
Model classes follow the BaseModel pattern with:
- CRUD operations via database abstraction
- Validation hooks
- Event emission on state changes
- Transaction support via
TransactionHandler
8.3 Server Route Architecture
Routes follow a RESTful pattern under /api/:
| Route Group | Endpoints | Purpose |
|---|---|---|
api/items |
GET, PUT, DELETE, children, delta | Core data synchronization |
api/sessions |
POST | Authentication |
api/users |
GET, POST, PATCH | User management |
api/shares |
POST, GET | Notebook sharing |
api/share_users |
PATCH, GET | Share membership |
api/locks |
POST, GET | Sync lock management |
api/batch |
POST | Batch operations |
api/batch_items |
POST | Batch item operations |
api/events |
GET, POST | Event stream |
api/transcribe |
GET, POST | Audio transcription |
api/saml |
GET, POST | SAML SSO |
api/login_with_code |
GET | Code-based login (SAML flow) |
api/application_auth |
GET, POST | Application authorization |
api/ping |
GET | Health check |
api/debug |
GET | Debug diagnostics |
8.4 Complete Server API Reference
Items API
GET /api/items/:id Retrieve item content
PUT /api/items/:id Update item content
GET /api/items/:id/delta Get changes since cursor
GET /api/items/:id/children List child items
The items API is the backbone of synchronization. Each item is identified by a unique ID and versioned. The delta endpoint enables efficient incremental sync by returning only items modified since the provided cursor.
Sessions API
POST /api/sessions Create session (authenticate)
Request body: { "email": "...", "password": "..." }
Returns a session token for subsequent authenticated requests.
Users API
POST /api/users Create user
GET /api/users List users
GET /api/users/:id Get user details
GET /api/users/:id/public_key Get user's public key (E2EE sharing)
PATCH /api/users/:id Update user
Shares API
POST /api/shares Create share (notebook sharing)
GET /api/shares List shares
GET /api/shares/:id Get share details
POST /api/shares/:id/users Add user to share
GET /api/shares/:id/users List share members
Share Users API
GET /api/share_users List share user associations
PATCH /api/share_users/:id Update share user (accept/reject)
Locks API
POST /api/locks Acquire sync lock
GET /api/locks List active locks
Sync locks prevent concurrent modifications during synchronization. Lock types support exclusive and shared access patterns.
Batch API
POST /api/batch Execute batch operations
POST /api/batch_items Execute batch item operations
Used by sync clients to bundle multiple operations into a single HTTP request, reducing latency for operations that would otherwise require sequential round-trips.
Events API
GET /api/events List events
POST /api/events Create event
The events system enables server-side event tracking and notification.
Transcription API
POST /api/transcribe Submit audio for transcription
GET /api/transcribe/:id Get transcription result
Used by the voice typing feature. Processes audio files through the transcription service.
SAML API
GET /api/saml SAML metadata / initiate SSO
POST /api/saml SAML assertion consumer service
GET /api/login_with_code/:id Complete SAML login flow
Utility Endpoints
GET/POST /api/application_auth Application authentication
GET /api/ping Health check (returns "JoplinServer")
GET /api/debug Debug information (admin only)
8.5 Editor Stack
Joplin's editor is a sophisticated multi-layer system:
┌─────────────────────────────────────────────────────┐
│ Editor Stack │
│ │
│ Desktop: │
│ ┌──────────────┐ ┌──────────┐ ┌──────────────┐ │
│ │ CodeMirror 6 │ │ TinyMCE │ │ Plain Editor │ │
│ │ (Markdown) │ │ (WYSIWYG)│ │ (Fallback) │ │
│ └──────────────┘ └──────────┘ └──────────────┘ │
│ │
│ Mobile: │
│ ┌──────────────┐ ┌────────────┐ │
│ │ CodeMirror │ │ ProseMirror│ │
│ │ (Markdown) │ │ (WYSIWYG) │ │
│ └──────────────┘ └────────────┘ │
│ │
│ CLI: │
│ ┌──────────────────────────────┐ │
│ │ terminal-kit Text Widget │ │
│ └──────────────────────────────┘ │
│ │
│ Custom Components: │
│ ├── JoplinLists (TinyMCE plugin) │
│ ├── FloatingButtonBar (mobile toolbar) │
│ ├── ButtonRow (action buttons) │
│ └── ElementObserver (DOM monitoring) │
└─────────────────────────────────────────────────────┘
8.6 Plugin System Deep Dive
8.6.1 Plugin Lifecycle
Discovery -> Validation -> Loading -> Sandbox Init -> Execution -> Teardown
- Discovery: plugins found in plugin directory or via
plugins.devPluginPaths - Validation: manifest checked (ID: 16-256 chars, semver version, required fields)
- Loading: plugin bundle loaded into sandboxed context
- Sandbox Init:
executeSandboxCallcreates isolated execution environment - Execution: plugin registers handlers, commands, views
- Teardown: cleanup on disable/uninstall
8.6.2 Plugin API Surface
// JoplinData — CRUD access to Joplin entities
joplin.data.get(['notes'], { fields: ['id', 'title'] });
joplin.data.post(['notes'], null, { title: 'New Note', body: '...' });
joplin.data.put(['notes', noteId], null, { title: 'Updated' });
joplin.data.delete(['notes', noteId]);
// JoplinImaging — image manipulation
joplin.imaging.createFromPath(imagePath);
joplin.imaging.resize(handle, { width: 800 });
Supported data types: notes, folders, tags, resources, search
8.6.3 Content Scripts
Content scripts extend the renderer with custom markdown tokens and rendering logic:
// Content script pattern
module.exports = function(context) {
return {
plugin: function(markdownIt, options) {
// Register custom markdown-it plugin
},
assets: function() {
return [{ name: 'style.css' }];
}
};
};
8.6.4 Default Plugins
The default-plugins package manages built-in plugins that ship with the desktop application. These are built through a pipeline that:
- Resolves plugin source from the plugin repository
- Builds each plugin
- Packages them into the desktop distribution
- Handles updates through the plugin update mechanism
8.6.5 Plugin Generator
The generator-joplin package provides a Yeoman generator for scaffolding new plugins:
npx yo joplin
# Prompts for plugin name, description, etc.
# Generates plugin skeleton with manifest, entry point, and build config
8.7 Markdown Renderer
The renderer package converts Markdown to HTML with extensive plugin support:
| Plugin | Setting | Description |
|---|---|---|
| KaTeX | markdown.plugin.katex |
LaTeX math rendering |
| mhchem | markdown.plugin.mhchem |
Chemical formula rendering |
| Mark | markdown.plugin.mark |
Highlight/mark text |
| Footnote | markdown.plugin.footnote |
Footnote support |
| TOC | markdown.plugin.toc |
Table of contents generation |
| Sub/Sup | markdown.plugin.sub / .sup |
Subscript/superscript |
| Definition List | markdown.plugin.deflist |
Definition lists |
| Abbreviation | markdown.plugin.abbr |
Abbreviation tooltips |
| Emoji | markdown.plugin.emoji |
Emoji shortcodes |
| Insert | markdown.plugin.insert |
Insert (underline) |
| Multitable | markdown.plugin.multitable |
Complex tables |
| Soft breaks | markdown.plugin.softbreaks |
Treat newlines as <br> |
8.8 Import/Export Architecture
8.8.1 HTML Processing
The turndown package (forked) converts HTML to Markdown, with turndown-plugin-gfm adding GitHub Flavored Markdown support (tables, strikethrough, task lists). The fork-htmlparser2 and fork-sax packages provide the underlying HTML/XML parsing.
Utility classes:
HtmlUtils— HTML sanitization and transformationMarkupLanguageUtils— markup type detection and conversionWelcomeUtils— initial welcome note generation
8.8.2 OneNote Import
The onenote-converter package is a Rust application compiled to WASM:
- Parses OneNote
.onefiles - Converts OneNote page structure to Joplin notes
- Handles embedded images and attachments
- Known issue #15132: import can hang during postprocessing for complex notebooks
8.9 OCR System
Joplin implements Optical Character Recognition for searchable attachments:
| Feature | Implementation |
|---|---|
| Local OCR | Tesseract (desktop) |
| Handwritten text | Experimental, Joplin Server/Cloud only |
| PDF modes | normal (fast, standard text) / accessible (enhanced for screen readers) |
| Language data | Cached locally after first download |
| Resource processing | Sequential, known issue #14805 (stops after ~5 resources) |
8.10 Transcription Service
The transcribe package provides server-side audio transcription:
- Accepts audio file uploads via
POST /api/transcribe - Returns transcription results via
GET /api/transcribe/:id - Used by the voice typing feature on mobile (backed by
whisper-voice-typingC++/Whisper on Android)
8.11 Key Service Classes
| Class | Package | Responsibility |
|---|---|---|
Setting |
lib | Configuration management, persistence, validation |
BaseModel |
lib | Data model base (CRUD, validation, serialization) |
BaseItem |
lib | Sync-capable item base (encryption, sharing) |
SearchEngine |
lib | Full-text search (FTS4/FTS5), indexing |
SyncTargetRegistry |
lib | Sync backend management |
DecryptionWorker |
lib | Background E2EE decryption |
AlarmService |
lib | To-do reminder scheduling |
CommandService |
lib | Command pattern (undo, keyboard shortcuts) |
PluginService |
lib | Plugin lifecycle management |
ExternalEditWatcher |
lib | File change monitoring for external editing |
ResourceEditWatcher |
lib | Attachment file change monitoring |
ProfileConfig |
lib | Multi-profile management |
EventManager |
lib | Pub/sub event bus |
Logger |
lib | Structured logging with levels |
FileLocker |
lib | File-based locking |
Registry |
lib | Service locator / dependency container |
Time |
lib | Time utilities and mocking support |
Cache |
server | In-memory TTL cache |
Models |
server | Model factory |
Response |
server | HTTP response builder |
PerformanceTimer |
server | Request timing metrics |
Bridge |
app-desktop | Electron main/renderer IPC |
AppGui |
app-cli | terminal-kit TUI management |
LinkSelector |
app-cli | Interactive link selection |
ResourceServer |
app-cli | Local resource serving for CLI rendering |
8.12 CLI Command Reference
The CLI application (app-cli) includes 42 command classes covering full Joplin functionality:
| Category | Commands |
|---|---|
| Notes | mknote, rmnote, edit, cat, set, todo, done, undone |
| Notebooks | mkbook, rmbook, use, notebook |
| Tags | tag (add, remove, list) |
| Search | search |
| Sync | sync, config (sync settings) |
| Import/Export | import, export |
| E2EE | e2ee (enable, disable, decrypt) |
| System | status, version, help, config, ls, geoloc |
8.13 Build System Details
8.13.1 Workspace Structure
// Root package.json (simplified)
{
"workspaces": ["packages/*"],
"packageManager": "yarn@4.9.2"
}
8.13.2 Build Order
Package dependencies enforce build order:
utils -> lib -> renderer -> editor -> app-desktop
-> app-mobile
-> app-cli
-> server
fork-htmlparser2 -> turndown -> lib
onenote-converter (standalone Rust/WASM build)
8.13.3 Testing
| Framework | Usage |
|---|---|
| Jest | Unit tests across all packages |
| Playwright | Integration tests (desktop E2E) |
| Custom test harness | Server integration tests |
# Run all tests
yarn test
# Run tests for specific package
cd packages/lib && yarn test
# Playwright (desktop integration)
cd packages/app-desktop && yarn test-integration
9. Integration Points
9.1 Sync Target Integration
9.1.1 WebDAV (Generic)
Sync Target ID: 6
Protocol: WebDAV (RFC 4918)
Authentication: Basic or Digest
Path: Full URL to WebDAV endpoint
WebDavApi handles all WebDAV protocol operations including PROPFIND, MKCOL, PUT, GET, DELETE with retry logic and error mapping.
9.1.2 Nextcloud
Sync Target ID: 5
Protocol: WebDAV (Nextcloud-specific paths)
Authentication: Basic auth or app passwords
Path: https://nextcloud.example.com/remote.php/webdav/Joplin
Nextcloud support builds on the WebDAV target with Nextcloud-specific endpoint detection.
9.1.3 OneDrive
Sync Target ID: 3 (personal) / 4 (business)
Protocol: Microsoft Graph API
Authentication: OAuth 2.0
Uses Microsoft's authentication flow. Business accounts (ID 4) use different Graph API endpoints.
9.1.4 Dropbox
Sync Target ID: 7
Protocol: Dropbox API v2
Authentication: OAuth 2.0
DropboxApi wraps the Dropbox HTTP API with session management and file operations.
9.1.5 Amazon S3
Sync Target ID: 8
Protocol: S3 (AWS SDK)
Authentication: Access Key + Secret
Endpoint: Configurable (supports MinIO, Wasabi, etc.)
SyncTargetAmazonS3 supports any S3-compatible storage provider.
9.1.6 Filesystem
Sync Target ID: 2
Protocol: Local filesystem
Path: Absolute path to sync directory
No network required. Useful for USB-drive sync or local backup.
9.1.7 Joplin Server / Joplin Cloud
Sync Target ID: 9 (Server) / 10 (Cloud) / 11 (Server + SAML)
Protocol: Joplin REST API
Authentication: Session token or SAML
The most feature-complete sync target, supporting notebook sharing, E2EE key exchange, and delta sync.
9.2 Web Clipper Integration
The Web Clipper browser extension communicates with the desktop/CLI application via the local REST API:
Desktop/CLI <--REST API (localhost:41184)--> Web Clipper Extension
Clipping modes:
- Simplified page (article extraction)
- Complete page (full HTML)
- Screenshot (visible area)
- URL only (bookmark)
- Selection (highlighted text)
9.3 External Editor Integration
Joplin supports external text editors via ExternalEditWatcher:
- User triggers "Edit in external editor"
- Joplin writes note content to a temporary file
- External editor opens the file
ExternalEditWatchermonitors for changes (filesystem events)- Changes are synced back to the note in real-time
Configure via editor setting (e.g., code, vim, subl).
9.4 REST API Integration (Client-Side)
Third-party applications and scripts interact with Joplin via the local REST API:
# List all notes
curl http://localhost:41184/notes?token=YOUR_TOKEN
# Create a note
curl -X POST http://localhost:41184/notes?token=YOUR_TOKEN \
-H "Content-Type: application/json" \
-d '{"title": "API Note", "body": "Created via REST API"}'
# Search notes
curl "http://localhost:41184/search?query=keyword&token=YOUR_TOKEN"
# Get all tags
curl http://localhost:41184/tags?token=YOUR_TOKEN
# Get all folders (notebooks)
curl http://localhost:41184/folders?token=YOUR_TOKEN
# Get a specific resource (attachment)
curl http://localhost:41184/resources/RESOURCE_ID?token=YOUR_TOKEN
# Ping (no auth required)
curl http://localhost:41184/ping
All data endpoints require the token query parameter matching the api.token setting.
9.5 PDF Viewer
The pdf-viewer package provides embedded PDF viewing in the desktop application:
- React-based PDF rendering
- Integrated with the resource system
- Supports OCR text overlay for searchable PDFs
9.6 HTML Pack
The htmlpack package creates self-contained HTML files:
- Bundles CSS, images, and scripts inline
- Used for export and note sharing
- Produces single-file HTML that renders identically to the original
10. Troubleshooting
10.1 Server Issues
Problem: "Timeout trying to connect to database"
Cause: PostgreSQL is unreachable or not accepting connections.
Resolution:
- Verify PostgreSQL is running:
pg_isready -h <host> -p <port> - Check connection parameters in database config file
- Verify firewall rules allow connections on the PostgreSQL port
- Check PostgreSQL
pg_hba.conffor authentication rules - Increase connection timeout if the database is under heavy load
Problem: "Postgres version not supported"
Cause: The installed PostgreSQL version is below the minimum enforced by Joplin Server.
Resolution: Upgrade PostgreSQL to the required minimum version. Check server startup logs for the exact minimum version required.
Problem: "One or more migrations need to be applied"
Cause: Database schema is behind the server version.
Resolution: This is informational — migrations run automatically on startup. Allow the server to complete migration before serving traffic. For large databases, schedule maintenance downtime.
Problem: "The device time drift is ${timeDrift}ms"
Cause: Client clock is out of sync with the server beyond MAX_TIME_DRIFT threshold.
Resolution:
- Sync client system clock via NTP
- If time drift checking is problematic, set
MAX_TIME_DRIFT=0on the server to disable it (reduces replay attack protection)
Tip
On Docker deployments, the container's clock inherits from the host. If the host clock drifts (common in VMs that suspend/resume), restart the container or sync the host clock.
Problem: "CSRF token is missing" / "Invalid CSRF token"
Cause: State-changing request lacks a valid CSRF token.
Resolution: Ensure the client obtains a CSRF token from the session creation response and includes it in subsequent requests. API clients using session auth must handle CSRF; token-based auth (API keys) may bypass CSRF depending on server configuration.
Problem: "Invalid or missing session"
Cause: Request lacks authentication or uses an invalid session token.
Resolution:
- Re-authenticate via
POST /api/sessions - Verify the session token is included in request headers
- Check that the user account is not disabled
Problem: "Your session has expired"
Cause: Session exceeded its TTL.
Resolution: Re-authenticate. Consider adjusting session TTL in server configuration for environments where long-lived sessions are acceptable.
Caution
Extending session TTL increases the window of vulnerability if a session token is compromised. Balance usability against security requirements.
Problem: "This account is disabled"
Cause: Administrator disabled the user account.
Resolution: An admin must re-enable the account via PATCH /api/users/:id or the admin web interface.
Problem: "SAML support is disabled"
Cause: SAML SSO is not configured on the server.
Resolution: Enable SAML in server configuration. SAML requires Identity Provider metadata, assertion consumer service URL, and entity ID configuration.
10.2 Sync Issues
Problem: Sync conflict — duplicate notes appear
Cause: The same note was modified on two devices before either synced.
Resolution: Joplin creates conflict notes (prefixed with "Conflict") to preserve both versions. Manually merge and delete the conflict copy. Reduce sync interval to minimize conflict window.
Problem: Sync stalls at "Downloading resources"
Cause: Large attachments or slow network.
Resolution:
- Switch resource download mode to
manualorauto - Reduce
sync.maxConcurrentConnectionson slow networks - Check for very large resources (> 100 MB) that may timeout
Problem: Sync fails with lock errors
Cause: A previous sync session did not release its lock (crash, timeout, network interruption).
Resolution: Locks have TTL-based expiration. Wait for the lock to expire, or manually clear stale locks via GET /api/locks and removing expired entries.
Problem: ErrorResyncRequired from server
Cause: Server state diverged too far from client state for delta sync.
Resolution: The client must perform a full resync. This happens automatically but may be slow for large accounts. Ensure adequate time and stable network for the full sync.
Problem: Sync to S3 fails with access denied
Cause: Incorrect S3 credentials or bucket permissions.
Resolution:
- Verify access key and secret key
- Ensure the bucket exists and the IAM user has read/write permissions
- For non-AWS S3 providers (MinIO, Wasabi), verify the endpoint URL is correct
- Check bucket region configuration
10.3 Client Issues
Problem: Search returns no results despite matching notes existing
Cause: Full-text search index is corrupted or not built.
Resolution:
- Check
db.ftsEnabledsetting - Trigger a full reindex via Tools -> Rebuild search index (desktop)
- Known issue #13322: search can crash with very old profile data — if reindex fails, export notes, create fresh profile, reimport
Warning
The search index rebuild can be slow for large note collections (10,000+ notes). It runs in the background but may impact UI responsiveness during the process.
Problem: OCR stops processing after a few resources
Cause: Known issue #14805 — OCR processing halts after approximately 5 resources.
Resolution: Restart the application to resume OCR processing. This is a known bug where the OCR queue stalls.
Problem: To-do reminders not firing
Cause: AlarmService requires platform-specific notification permissions.
Resolution:
- Desktop: check system notification permissions for Joplin
- Mobile: verify notification permissions in OS settings
- Ensure the alarm time is set correctly (check timezone)
Problem: External editor changes not detected
Cause: ExternalEditWatcher relies on filesystem events that may not fire on network drives or certain filesystems.
Resolution:
- Use a local filesystem for the Joplin data directory
- Some editors write to a temporary file then rename — verify your editor's save behavior
- Check that the
editorsetting points to the correct executable
Problem: Desktop application shows blank/white screen
Cause: Electron rendering failure, often caused by GPU driver issues.
Resolution:
- Try launching with
--disable-gpuflag - Update GPU drivers
- Delete cache directory (
%APPDATA%\joplin-desktop\Cacheon Windows)
Problem: Mobile scroll rendering issues
Cause: Known issue #15141 — regression in scroll position calculation.
Resolution: Update to the latest mobile release. If the issue persists, switch between Markdown and WYSIWYG editor modes as a workaround.
Problem: Multi-window note focus issues (desktop)
Cause: Known issue #15106 — focus management across multiple Joplin windows.
Resolution: Click directly on the note editor to restore focus. This is a known UI bug in multi-window mode.
10.4 Encryption Issues
Problem: "Could not decrypt" errors on notes
Cause: Master key password not available for decryption.
Resolution:
- Go to Encryption settings
- Enter the master password for the listed master key
DecryptionWorkerwill process pending items in the background
Problem: E2EE enabled but sharing not working
Cause: PPK (Public-Private Key) not generated or exchanged.
Resolution:
- Ensure both users have E2EE enabled
- Verify PPK generation completed (check Encryption settings)
- The sharing user must have the recipient's public key
Note
Notebook sharing with E2EE requires that both parties have completed the PPK key exchange. This happens automatically through the sync target but requires both users to sync after enabling E2EE.
Problem: Re-encryption requested but not completing
Cause: encryption.shouldReencrypt is set but the process stalled.
Resolution:
- Ensure the application is running and syncing
- Re-encryption processes items in batches during idle time
- For large note collections, this can take hours — leave the application running
10.5 Build Issues
Problem: yarn install fails with workspace resolution errors
Cause: Yarn 4.9.2 workspace dependency resolution failure.
Resolution:
- Delete
.yarn/cacheandnode_modulesin all packages - Run
yarn installfrom the repository root - Ensure you are using Yarn 4.9.2 (check
.yarnrc.yml)
Problem: OneNote converter WASM build fails
Cause: Missing Rust toolchain or wasm-pack.
Resolution:
# Install Rust
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
# Install wasm-pack
cargo install wasm-pack
# Build
cd packages/onenote-converter
wasm-pack build
Problem: Electron builder fails on Linux
Cause: Missing native dependencies for packaging.
Resolution:
# Ubuntu/Debian
sudo apt-get install -y rpm fakeroot dpkg
# For AppImage
sudo apt-get install -y libfuse2
Problem: React Native build fails (Android)
Cause: Android SDK or NDK configuration issues.
Resolution:
- Verify
ANDROID_HOMEenvironment variable - Install required SDK platforms via Android Studio SDK Manager
- For
whisper-voice-typing: ensure NDK is installed and configured
Problem: React Native build fails (iOS)
Cause: CocoaPods or Xcode issues.
Resolution:
cd packages/app-mobile/ios
pod install --repo-update
Ensure Xcode command line tools are selected: xcode-select --install
10.6 Performance Issues
Problem: Slow startup with large note collection
Cause: SQLite database loading and index initialization.
Resolution:
- Ensure SQLite WAL mode is active (Joplin sets this automatically)
- Reduce revision retention (
revisionService.ttlDays) - Archive old notebooks to reduce active note count
Tip
SQLite VACUUM can reclaim space after bulk deletions. Access via the Joplin developer console:
await this.app().database().exec('VACUUM'). This locks the database briefly but can significantly reduce file size.
Problem: High memory usage during sync
Cause: Synchronizing many large resources simultaneously.
Resolution:
- Reduce
sync.maxConcurrentConnectionsto 1-2 - Switch resource download mode to
manualorauto - Increase Node.js heap size:
--max-old-space-size=4096
Problem: Slow markdown rendering
Cause: Complex notes with many plugins enabled.
Resolution:
- Disable unused markdown plugins (KaTeX, mhchem, etc.)
- Split very long notes (> 10,000 words) into smaller notes
- Disable WYSIWYG mode if not needed (Markdown mode is faster)
Appendix A: File API Implementations
Each sync target implements a FileApi interface:
| Class | Target | Key Methods |
|---|---|---|
FileApi (base) |
Abstract | get, put, delete, list, mkdir, stat |
FileApiDriverLocal |
Filesystem | Direct FS operations |
WebDavApi |
WebDAV/Nextcloud | HTTP PROPFIND, PUT, GET, DELETE, MKCOL |
DropboxApi |
Dropbox | Dropbox HTTP API v2 |
FileApiDriverAmazonS3 |
S3 | AWS SDK operations |
FileApiDriverOneDrive |
OneDrive | Microsoft Graph API |
FileApiDriverMemory |
Memory | In-memory map (testing) |
Appendix B: Server Middleware Stack
The Koa middleware stack processes requests in order:
- Error handler — catches exceptions, maps to
ApiErrorsubclasses - CORS — cross-origin request handling
- Body parser — request body parsing (JSON, multipart)
- CSRF protection — validates CSRF tokens for state-changing requests
- Rate limiting — per-user/IP request throttling
- Authentication — session validation, token verification
- Route handler — business logic execution
- Response — HTTP response formatting via
Responseclass
Appendix C: Database Schema (Client SQLite)
Core tables in the client SQLite database:
| Table | Purpose | Key Fields |
|---|---|---|
notes |
Note content and metadata | id, parent_id, title, body, is_todo, todo_completed, markup_language |
folders |
Notebooks | id, title, parent_id |
tags |
Tag definitions | id, title |
note_tags |
Note-tag associations | id, note_id, tag_id |
resources |
Attachment metadata | id, title, mime, filename, size |
resource_local_states |
Local resource download state | resource_id, fetch_status |
master_keys |
E2EE master keys (encrypted) | id, content, checksum |
settings |
Configuration (DB-backed) | key, value |
deleted_items |
Soft-deleted items pending sync | id, item_type, item_id |
revisions |
Note revision history | id, parent_id, title_diff, body_diff |
search |
FTS virtual table | content (indexed) |
alarms |
To-do reminders | id, note_id, trigger_time |
item_changes |
Change tracking for sync | id, item_type, item_id, type |
note_resources |
Note-resource associations | note_id, resource_id |
key_values |
Key-value metadata store | id, key, value, type |
Schema migrations are versioned and run automatically on application upgrade.
Appendix D: Environment Matrix
| Feature | Desktop | Mobile | CLI | Server |
|---|---|---|---|---|
| E2EE | Yes | Yes | Yes | N/A (pass-through) |
| Plugins | Yes | No | No | N/A |
| External editor | Yes | No | Yes | N/A |
| Web Clipper | Yes | No | Yes | N/A |
| OCR | Yes (Tesseract) | No | No | Yes (handwritten, experimental) |
| Voice typing | No | Yes (Android) | No | Yes (transcribe API) |
| WYSIWYG | Yes (TinyMCE) | Yes (ProseMirror) | No | N/A |
| Multiple profiles | Yes | No | No | N/A (multi-user) |
| Geolocation | No | Yes | Yes | N/A |
| System tray | Yes | N/A | N/A | N/A |
| PDF viewer | Yes | No | No | N/A |
| Note sharing | Yes | Yes | Yes | Yes (required) |
| SAML SSO | N/A | N/A | N/A | Yes |
Appendix E: Known Issues Reference
| Issue | Component | Description | Impact |
|---|---|---|---|
| #15132 | onenote-converter | OneNote import hangs during postprocessing | Import failure for complex notebooks |
| #13322 | lib (SearchEngine) | Search crash with old profile data | Search unusable until reindex |
| #14805 | lib (OCR) | OCR stops processing after ~5 resources | Incomplete OCR coverage |
| #15141 | app-mobile | Scroll rendering regression | Reading experience degraded |
| #15106 | app-desktop | Multi-window note focus issues | Usability issue in multi-window |