# Joplin — Administrator & Developer Guide

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](/laurent22/joplin/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:

```bash
# 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:

```yaml
# 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
# 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

```bash
# 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

```bash
# 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:

```typescript
// 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

```typescript
// 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:

```javascript
// 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:

```bash
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

```json
// 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 |

```bash
# 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:

```bash
# 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**:
```bash
# 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**:
```bash
# 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**:
```bash
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 |

---

_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. Unofficial; not affiliated with or reviewed by the project._
