Jottery is a simple, searchable, scratchpad/notes application. It is designed to be self-hosted and accessible from both a web browser and a terminal. The core idea is to provide a secure place for your notes, with all data being end-to-end encrypted. It runs as a web app or in a terminal, the sync server is optional. All data are stored locally unless explicitly configured to sync to an endpoint of your choosing (which stores notes, tags, and attachments in their encrypted forms and never sees the unencrypted data).
Most note-taking apps are either too heavy (designed for long-form documents) or too limited (plain text files). Jottery is a lightweight scratchpad that allows notes to be easily created, tagged and found again. It handles code snippets, searchable tags, and includes a handy "repl" calculator. It provides both web and terminal clients that sync seamlessly, so you can access your notes from a browser or SSH session. A script to convert Evernote .enex files into something Jottery can import is included for user convenience.
- End-to-End Encryption: All your notes are encrypted on your device before being stored/sent to the sync server. The server only stores encrypted blobs of data.
- Multi-User Support: Multiple users can use the same server, each with their own encrypted notes and devices.
- Admin Approval Workflow: New user registrations require admin approval for controlled access.
- Cross-Platform: Access your notes from a web client or a Terminal User Interface (TUI).
- Self-Hostable: You have full control over your data by hosting the sync server yourself.
- Device Management: Register multiple devices per user, each with its own API key.
- Admin Dashboard: Web-based interface for managing users, viewing statistics, and monitoring server activity.
- Advanced Search: Powerful full-text search with modifiers:
has:attachment- filter notes with attachmentscreated:>2024-01-01,created:<2024-06-30,created:2024-01-01..2024-06-30- date filtersmodified:>2024-01-01- modification date filterswords:>100,words:<50,words:50..200- word count filters
- Multi-Select & Bulk Operations: Select multiple notes (Ctrl/Cmd+click, Shift+click, Ctrl/Cmd+A) for bulk actions:
- Add or remove tags from multiple notes
- Export selected notes to JSON
- Delete multiple notes at once
- Tagging: Organise your notes with tags.
- Attachments: Add, preview, and download attachments to your notes.
- Code Snippets: A rich text editor with support for various programming languages.
- Quick Delete: Delete notes instantly from the list view (hover for delete button, or use configurable keyboard shortcut).
- Keyboard Shortcuts: Fully customisable keyboard shortcuts for common actions.
- Many handy features: Export notes, preview HTML, document info, basic versioning, and markdown documents in-editor.
The easiest way to get started is with Docker. Pre-built multi-architecture images (amd64, arm64) are available.
-
Pull and run the image:
docker run -d \ --name jottery \ -p 8088:8088 \ -v jottery-data:/app/data \ ghcr.io/seesee/jottery:latest
The web interface will be available at
http://localhost:8088. The admin dashboard is athttp://localhost:8088/admin.
-
Clone the repository:
git clone https://github.com/seesee/jottery.git cd jottery -
Run with Docker Compose:
docker-compose up -d
The web interface will be available at
http://localhost:8088.
If you prefer to build the image yourself:
git clone https://github.com/seesee/jottery.git
cd jottery
docker build -t jottery .
docker run -d --name jottery -p 8088:8088 -v jottery-data:/app/data jotteryYou can configure the server using environment variables:
| Variable | Default | Description |
|---|---|---|
DEFAULT_ADMIN_EMAIL |
admin@localhost |
Default admin account email |
DEFAULT_ADMIN_PASSWORD |
changeme |
Default admin account password |
DATABASE_URL |
sqlite:jottery.db |
Database file path |
PORT |
3030 |
Internal server port |
SESSION_EXPIRY_DAYS |
7 |
Admin session expiry in days |
DEFAULT_STORAGE_QUOTA_MB |
1000 |
Default user storage quota in MB |
MAX_PAYLOAD_SIZE |
5242880 |
Max upload size in bytes (5MB) |
CORS_ALLOWED_ORIGINS |
(none) | Comma-separated list of allowed CORS origins |
Example with custom admin credentials:
docker run -d \
--name jottery \
-p 8088:8088 \
-v jottery-data:/app/data \
-e [email protected] \
-e DEFAULT_ADMIN_PASSWORD=your-secure-password \
ghcr.io/seesee/jottery:latest-
Access the admin dashboard:
Navigate to
http://localhost:8088/adminand login with the default credentials:- Email:
admin@localhost - Password:
changeme
⚠️ IMPORTANT: Change the default admin password immediately, or set custom credentials via environment variables before first run. - Email:
-
Create your first user account:
- Register a new user account via the web UI at
http://localhost:8088 - Login to the admin dashboard and approve the new user
- The approved user can now register devices and start syncing notes
- Register a new user account via the web UI at
If you prefer to run the components manually, you can follow these steps:
-
Clone the repository:
git clone https://github.com/seesee/jottery.git cd jottery -
Web Client:
npm install npm run dev
-
Sync Server:
cd server cargo run -
TUI Client:
You can download a pre-compiled binary for your platform from the releases section in the web app, or build it from source:
cd tui cargo run
Rich Editor with Syntax Highlighting

Multi-Select and Bulk Operations

Jottery works great on mobile devices too.
- Sync: The sync mechanism is quite robust but very basic. Updates are sent periodically and the last version to be received "wins". To mitigate against accidental deletions, the previous version is also stored.
- Security: Use a strong password -- all data blobs (notes, tags, attachments) are encrypted using it. If you forget your password, there is no recovery process. You will lose all your notes and will need to start over. Because it's handy for development purposes, there is a mechanism to store the password in both web and tui app (this is NOT synced) so you don't need to constantly input your password -- but if you use this, your notes are basically plain text to anyone with access to your device/db files. I suggest using a password manager.
- Sync Server: All sync accounts need to be approved by an admin. Admins can see "username" (email address is suggested but does not need to be "real"), the total number of notes and the combined size (in kb) of the notes held by that user.
Jottery consists of three main components:
A modern web application that provides a rich user experience for managing your notes.
A lightweight and fast terminal user interface for those who prefer to work in the terminal. The TUI includes powerful command-line tools for quick note operations:
CLI Commands:
jottery note- Create a note with your$EDITOR, or pipe content directly:echo "Quick note" | jottery note ls -la | jottery note -t system,logs
jottery list- List all notes with optional tag filteringjottery search <query>- Search notes by content or tagsjottery show <id>- Display a specific notejottery sync- Manually trigger sync with serverjottery export/jottery import- Backup and restore notes
CLI Features:
- Stored password support - No password prompts when you've saved your password
- Pipe content directly - Capture command output as notes instantly
- Auto-sync - Notes sync to server automatically after creation (if configured)
- Tag support - Add tags to piped content with
-t tag1,tag2
TUI Multi-Select & Bulk Operations:
Space- Toggle selection of current noteCtrl+A- Select all filtered notesShift+V- Range select from last selected to currentEscape- Clear selectiont(in multi-select mode) - Add tags to selected notesd(in multi-select mode) - Delete selected notes (with confirmation)e(in multi-select mode) - Export selected notes to JSON file
Advanced Search (same modifiers as web client):
has:attachment,created:>DATE,modified:>DATE,words:>N, etc.
A simple server that stores your encrypted notes and syncs them between your clients.
Jottery supports multi-user deployments with an admin approval workflow:
- New users register with email and password (minimum 12 characters)
- Account enters "pending approval" state
- Admin approves the account via admin dashboard or CLI
- Approved users can register devices for sync
- Each user can register multiple devices (web, TUI, CLI)
- Each device receives a unique API key for sync operations
- Devices can be individually revoked by admins
- Separate from user accounts for better security
Access the web-based admin dashboard at http://your-server:3030/admin
Default credentials (
- Email:
admin@localhost - Password:
changeme
When using Docker, set custom admin credentials via environment variables (DEFAULT_ADMIN_EMAIL and DEFAULT_ADMIN_PASSWORD) before first run. See Environment Variables for details.
Admin Features:
- Approve/reject user registrations
- View user statistics (notes, devices, storage)
- Deactivate/reactivate user accounts
- Manage device registrations
- View audit logs of sync operations
- Monitor server statistics
For detailed information about Jottery's security architecture, deployment best practices, and threat model, see SECURITY.md.
- Framework: Svelte
- Language: TypeScript
- Styling: Tailwind CSS
- Editor: CodeMirror
- Database: IndexedDB
- Search: FlexSearch
- Framework: Ratatui
- Language: Rust
- Database: SQLite with SQLCipher
- Async Runtime: Tokio
- Framework: Axum
- Language: Rust
- Database: SQLite
- Async Runtime: Tokio
Download the standalone web application for local use:
| Version | File | Notes |
|---|---|---|
| 0.9.21 | jottery-web-0.9.21-offline.zip | Single HTML file + assets |
Usage: Extract and open index.html in any modern browser. All data is stored locally in IndexedDB.
file:// protocol), server sync is unavailable due to browser security restrictions. For sync functionality, serve via a local web server (e.g., python -m http.server 8000).
Pre-compiled binaries for the TUI client (Linux, macOS, Windows) are available from the "Releases" section of the web application.
Jottery uses a JSON-based format for importing and exporting notes. All data is decrypted during export and re-encrypted during import using your master password.
- Format: JSON with UTF-8 encoding
- Version:
1.0(specified inversionfield) - Security: Exported data contains decrypted note content, tags, and attachments
- Compatibility: Import works across different Jottery clients (Web, TUI)
{
"version": "1.0",
"exportDate": "2025-12-30T18:30:00.000Z",
"notes": [
{
"id": "550e8400-e29b-41d4-a716-446655440000",
"createdAt": "2025-12-25T10:00:00.000Z",
"modifiedAt": "2025-12-29T15:30:00.000Z",
"content": "# Meeting Notes\n\nDiscussed project timeline...",
"tags": ["work", "meetings", "project-alpha"],
"attachments": [
{
"filename": "diagram.png",
"mimeType": "image/png",
"data": "iVBORw0KGgoAAAANSUhEUgAAAAUA..."
}
],
"pinned": false,
"wordWrap": true,
"syntaxLanguage": "markdown"
}
]
}| Field | Type | Required | Description |
|---|---|---|---|
version |
string | Yes | Export format version (currently "1.0") |
exportDate |
string | Yes | ISO 8601 timestamp when export was created |
notes |
array | Yes | Array of exported notes (see Note Object below) |
| Field | Type | Required | Description |
|---|---|---|---|
id |
string | Yes | UUID v4 identifier for the note |
createdAt |
string | Yes | ISO 8601 timestamp when note was created |
modifiedAt |
string | Yes | ISO 8601 timestamp of last modification |
content |
string | Yes | Decrypted note content (plain text or markdown) |
tags |
array | Yes | Array of decrypted tag strings |
attachments |
array | Yes | Array of attachment objects (can be empty) |
pinned |
boolean | Yes | Whether note is pinned to top of list |
wordWrap |
boolean | No | Word wrap setting for editor (default: false) |
syntaxLanguage |
string | No | Syntax highlighting language ID (e.g., "javascript", "markdown") |
| Field | Type | Required | Description |
|---|---|---|---|
filename |
string | Yes | Decrypted original filename |
mimeType |
string | Yes | MIME type (e.g., "image/png", "application/pdf") |
data |
string | Yes | Base64-encoded file data (decrypted) |
When importing notes, you can choose one of three strategies:
merge(default): Import all notes with new UUIDs, preserving existing notesskip: Skip notes with IDs that already exist in your databasereplace: Delete existing notes with matching IDs and import new versions
{
"version": "1.0",
"exportDate": "2025-12-30T20:00:00.000Z",
"notes": [
{
"id": "123e4567-e89b-12d3-a456-426614174000",
"createdAt": "2025-12-30T19:00:00.000Z",
"modifiedAt": "2025-12-30T19:00:00.000Z",
"content": "Hello World",
"tags": [],
"attachments": [],
"pinned": false
}
]
}{
"version": "1.0",
"exportDate": "2025-12-30T20:15:00.000Z",
"notes": [
{
"id": "a3b2c1d0-e5f6-4a7b-8c9d-0e1f2a3b4c5d",
"createdAt": "2025-12-28T14:22:00.000Z",
"modifiedAt": "2025-12-30T16:45:00.000Z",
"content": "```javascript\nconst hello = () => console.log('Hello!');\n```",
"tags": ["code", "javascript", "snippets"],
"attachments": [
{
"filename": "screenshot.png",
"mimeType": "image/png",
"data": "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg=="
},
{
"filename": "README.txt",
"mimeType": "text/plain",
"data": "VGhpcyBpcyBhIHRlc3QgZmlsZQ=="
}
],
"pinned": true,
"wordWrap": false,
"syntaxLanguage": "javascript"
}
]
}The syntaxLanguage field supports all languages available in CodeMirror 6, including:
- Programming:
javascript,typescript,python,rust,go,java,cpp,c,csharp,php,ruby,swift,kotlin - Web:
html,css,scss,less,vue,svelte - Markup:
markdown,xml,yaml,json,toml - Shell:
shell,bash,powershell - Query:
sql,mysql,postgresql - And many more...
Security:
- Exported files contain unencrypted, plain-text data
- Store export files securely (encrypt with tools like GPG or use encrypted volumes)
- Delete export files after transferring if they contain sensitive information
Compatibility:
- Export format version
1.0is the current standard - Future versions may add fields but will maintain backward compatibility
- Timestamps must be valid ISO 8601 format with timezone (e.g.,
2025-12-30T20:00:00.000Z) - UUIDs must be valid UUID v4 format
File Size:
- Attachments are Base64-encoded, which increases size by ~33%
- Large exports with many attachments may take time to process
- Consider exporting in batches for very large note collections
Automation:
- The JSON format is designed to be easily parsed and generated by scripts
- Perfect for backup automation, migration tools, or external integrations
- Can be processed with
jq, Python, Node.js, or any JSON-compatible tool
Using jq to extract all tags:
jq '.notes[].tags[]' jottery-export-2025-12-30.json | sort -uUsing Python to filter notes by tag:
import json
with open('jottery-export.json', 'r') as f:
data = json.load(f)
work_notes = [n for n in data['notes'] if 'work' in n['tags']]
print(f"Found {len(work_notes)} work-related notes")Using Node.js to create a backup:
const fs = require('fs');
const exportData = {
version: '1.0',
exportDate: new Date().toISOString(),
notes: [
{
id: crypto.randomUUID(),
createdAt: new Date().toISOString(),
modifiedAt: new Date().toISOString(),
content: 'Automated backup note',
tags: ['backup', 'automated'],
attachments: [],
pinned: false
}
]
};
fs.writeFileSync('backup.json', JSON.stringify(exportData, null, 2));Jottery supports embedding attachments directly in your markdown notes. This allows you to create rich documents with inline images, PDFs, and other file types.
Use markdown link syntax to reference attachments by their filename:
Images (will be displayed inline when previewing markdown):
Other files (PDFs, documents, etc. - will show as clickable links):
[Attachment: filename.pdf](attachment:filename.pdf)- Upload an attachment to your note using the attachment area at the bottom
- Click the 📋 (clipboard) button next to the attachment
- Paste the markdown link into your note content
The correct syntax will be copied automatically:
- Images use
syntax - Other files use
[Attachment: filename](attachment:filename)syntax
When you preview a markdown note with attachment links:
- Images are rendered inline (displayed directly in the preview)
- PDFs, audio, and video show as clickable cards that open a full preview modal
- Text files can be previewed in the modal as well
- Other file types show a download link
- Images: PNG, JPG, GIF, SVG, WebP
- PDFs: Full page-by-page viewer with zoom controls
- Audio: MP3, WAV, OGG, etc.
- Video: MP4, WebM, etc.
- Text: TXT, JSON, XML, source code files
- Unsupported types: Download button to view externally
# Project Documentation
Here's the architecture diagram:

And here's the detailed specification:
[Attachment: specifications.pdf](attachment:specifications.pdf)
## Audio Notes
Meeting recording:
[Attachment: meeting-2025-01-02.mp3](attachment:meeting-2025-01-02.mp3)- Attachment filenames are encrypted in the database
- The markdown preview decrypts and resolves filenames automatically
- Renaming attachments will break existing markdown links (references are by filename)
- You can reference the same attachment multiple times in one note
All landing page screenshots and demos can be regenerated using the consolidated generation script:
./demo-generation/generate.shThis will:
- Generate 16 web screenshots (Playwright)
- Generate 4 TUI demo GIFs (VHS)
- Verify all files are present
See demo-generation/README.md for detailed documentation on modifying and regenerating individual demos.
This project is licensed under the MIT License - see the LICENSE file for details.










