Jesse A. Green / notebook

Live demo: the DocCompiler guide library as a static, read-only snapshot (October 2026). No backend, no accounts, nothing to submit.

Joplin — Administrator & Developer Guide

Generated May 2026 from laurent22/joplin at 698b59b

AI-generated documentation. The library is checked by sampling (three repositories so far); this guide was not individually reviewed. Verify against the project before relying on it.

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 lib package 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

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/ping for load balancer health checks and container orchestrator liveness probes. It returns a simple response without touching the database. Use /api/debug for 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 PerformanceTimer class 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:

  1. Client sends credentials to POST /api/sessions
  2. Server validates and returns session token
  3. Client includes token in subsequent requests
  4. 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) and JoplinImaging (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
  1. Discovery: plugins found in plugin directory or via plugins.devPluginPaths
  2. Validation: manifest checked (ID: 16-256 chars, semver version, required fields)
  3. Loading: plugin bundle loaded into sandboxed context
  4. Sandbox Init: executeSandboxCall creates isolated execution environment
  5. Execution: plugin registers handlers, commands, views
  6. 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:

  1. Resolves plugin source from the plugin repository
  2. Builds each plugin
  3. Packages them into the desktop distribution
  4. 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 transformation
  • MarkupLanguageUtils — markup type detection and conversion
  • WelcomeUtils — initial welcome note generation

8.8.2 OneNote Import

The onenote-converter package is a Rust application compiled to WASM:

  • Parses OneNote .one files
  • 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-typing C++/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:

  1. User triggers "Edit in external editor"
  2. Joplin writes note content to a temporary file
  3. External editor opens the file
  4. ExternalEditWatcher monitors for changes (filesystem events)
  5. 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:

  1. Verify PostgreSQL is running: pg_isready -h <host> -p <port>
  2. Check connection parameters in database config file
  3. Verify firewall rules allow connections on the PostgreSQL port
  4. Check PostgreSQL pg_hba.conf for authentication rules
  5. 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:

  1. Sync client system clock via NTP
  2. If time drift checking is problematic, set MAX_TIME_DRIFT=0 on 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:

  1. Re-authenticate via POST /api/sessions
  2. Verify the session token is included in request headers
  3. 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:

  1. Switch resource download mode to manual or auto
  2. Reduce sync.maxConcurrentConnections on slow networks
  3. 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:

  1. Verify access key and secret key
  2. Ensure the bucket exists and the IAM user has read/write permissions
  3. For non-AWS S3 providers (MinIO, Wasabi), verify the endpoint URL is correct
  4. 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:

  1. Check db.ftsEnabled setting
  2. Trigger a full reindex via Tools -> Rebuild search index (desktop)
  3. 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:

  1. Desktop: check system notification permissions for Joplin
  2. Mobile: verify notification permissions in OS settings
  3. 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:

  1. Use a local filesystem for the Joplin data directory
  2. Some editors write to a temporary file then rename — verify your editor's save behavior
  3. Check that the editor setting points to the correct executable

Problem: Desktop application shows blank/white screen

Cause: Electron rendering failure, often caused by GPU driver issues.

Resolution:

  1. Try launching with --disable-gpu flag
  2. Update GPU drivers
  3. Delete cache directory (%APPDATA%\joplin-desktop\Cache on 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:

  1. Go to Encryption settings
  2. Enter the master password for the listed master key
  3. DecryptionWorker will process pending items in the background

Problem: E2EE enabled but sharing not working

Cause: PPK (Public-Private Key) not generated or exchanged.

Resolution:

  1. Ensure both users have E2EE enabled
  2. Verify PPK generation completed (check Encryption settings)
  3. 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:

  1. Ensure the application is running and syncing
  2. Re-encryption processes items in batches during idle time
  3. 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:

  1. Delete .yarn/cache and node_modules in all packages
  2. Run yarn install from the repository root
  3. 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:

  1. Verify ANDROID_HOME environment variable
  2. Install required SDK platforms via Android Studio SDK Manager
  3. 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:

  1. Ensure SQLite WAL mode is active (Joplin sets this automatically)
  2. Reduce revision retention (revisionService.ttlDays)
  3. 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:

  1. Reduce sync.maxConcurrentConnections to 1-2
  2. Switch resource download mode to manual or auto
  3. Increase Node.js heap size: --max-old-space-size=4096

Problem: Slow markdown rendering

Cause: Complex notes with many plugins enabled.

Resolution:

  1. Disable unused markdown plugins (KaTeX, mhchem, etc.)
  2. Split very long notes (> 10,000 words) into smaller notes
  3. 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:

  1. Error handler — catches exceptions, maps to ApiError subclasses
  2. CORS — cross-origin request handling
  3. Body parser — request body parsing (JSON, multipart)
  4. CSRF protection — validates CSRF tokens for state-changing requests
  5. Rate limiting — per-user/IP request throttling
  6. Authentication — session validation, token verification
  7. Route handler — business logic execution
  8. Response — HTTP response formatting via Response class

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