Trinote
August 29, 2026 · View on GitHub
A native iOS client for self-hosted TriliumNext note-taking servers.
Features
- Connect with the same web session as Trilium (password + cookies + CSRF), not ETAPI
- Browse the full note tree with lazy loading and proper clone/branch semantics
- Read text notes (HTML), code notes, image notes, and file notes
- Search full-text across all notes via the server search API
- Edit note titles, content (HTML source or code), create/delete notes
- Attachments — upload from Photos/Files, download, share
- Office & EPUB previews —
.docx,.xlsx,.pptx, ODF, RTF and EPUB open in-app (Trilium v0.105+; falls back to Quick Look on older servers) - Offline cache — tree structure and recently opened notes cached locally
- Multiple servers — save and switch between server profiles
- Dark mode and numerous color options
Requirements
- iOS 17.0+
- Xcode 16.0+
- Swift 5.9+
- A self-hosted TriliumNext server (v0.95.x – v0.105.x supported; native
/apiroutes are pinned inlocal_notes/trilium_native_api_v0.95.md, with later-release deltas noted at the bottom of that file)
Setup
1. Generate the Xcode project
This project uses XcodeGen to generate the .xcodeproj:
brew install xcodegen
cd Trinote
xcodegen generate
2. Open in Xcode
open Trinote.xcodeproj
3. Configure signing
- Open the project settings
- Select the Trinote target
- Under Signing & Capabilities, set your development team
- Adjust the bundle identifier if needed
4. Build & Run
Select an iOS 17+ simulator or device and press Cmd+R.
Version & build number
MARKETING_VERSION and CURRENT_PROJECT_VERSION live in project.yml (XcodeGen’s source of truth). You can bump them either way:
- In Xcode — Trinote target → General → Identity → Version / Build, then build or run (
Cmd+R). That build ships the new values in the app (and share extension), and a pre-build script writes them back intoproject.ymlso a laterxcodegen generatewill not revert them. - In
project.yml— edit the two keys undersettings.base, then runxcodegen generatebefore building.
To sync project.yml from the current Xcode project without building:
./Scripts/sync_project_yml_version.sh
If you edit project.yml without running xcodegen generate, the next build may overwrite your YAML changes with the values still in the .xcodeproj.
Connecting to a Server
Password sign-in
- Enter your server URL (same origin you use in the browser)
- Enter your Trilium password (same as the web UI)
- Optional: Remember me — matches Trilium’s longer-lived session cookie
- TOTP is supported for password sign-in
SSO sign-in (OpenID / OAuth)
If your Trilium server uses built-in OAuth/OpenID (Authelia, Authentik, Keycloak, PocketID, Google OIDC, etc.), see docs/sso-setup.md for the full user setup guide (one-time server handler + app sign-in flow).
Short version:
- Add the
trinote-sso-handoffcustom request handler on your Trilium server (script indocs/trinote-sso-handoff.js). - In Trinote, enter your server URL and tap Sign in with SSO.
- Complete sign-in in Safari, return to Trinote, tap Continue.
Optional: Cloudflare Access service-token credentials under Advanced if API calls still need them after handoff.
Protected notes still require your Trilium document password after sign-in — SSO only replaces server login.
Self-signed certificates
If your server uses a self-signed certificate, you'll need to install and trust the CA certificate on your iOS device first (Settings → General → VPN & Device Management). The app allows arbitrary HTTP loads via ATS to support local networks.
Architecture
Trinote/
├── App/ # App entry, state, tab navigation
├── Core/
│ ├── API/ # TriliumClient (session + `/api`), models, WebSocket
│ ├── Models/ # Domain models, SwiftData cache models
│ ├── Persistence/ # SwiftData container, cache manager
│ ├── Security/ # Keychain token storage
│ └── Utilities/ # Logger, extensions
├── Features/
│ ├── Auth/ # Login, server profile management
│ ├── Tree/ # Note tree browsing
│ ├── Search/ # Full-text search with recents
│ ├── NoteDetail/ # Note viewing, editing, renderers
│ ├── Attachments/ # Photo/file upload
│ └── Settings/ # Settings, recents list
└── Resources/ # Info.plist, Assets
Key Design Decisions
- Native
/api+ sync — session cookies,sync/check+sync/changed, entity-change cursor; WebSocket debounces incremental sync - Notes ≠ Branches — notes and branches are separate entities; a note can appear in multiple tree locations (clones)
- Lazy tree loading — fetches children on demand to avoid loading the entire tree upfront
- Cache-first offline — falls back to cached data when the server is unreachable
- No embedded server — pure client that talks to your existing Trilium server
Testing
Run tests in Xcode (Cmd+U) or from the command line:
xcodebuild test -scheme Trinote -destination 'platform=iOS Simulator,name=iPhone 16'
Tests cover:
- API client (mock URLProtocol, request/response validation)
- Keychain save/load/delete
- Domain model mapping (notes, branches, attributes, tree nodes)
- Clone semantics (multi-parent notes)
- Error classification (auth, network, server errors)
Known Limitations
- Protected notes are able to be read (when password is entered) but not generated on this client
- Search is server-side only — no offline full-text search. Can jump to text references
If you want to discuss future work on Trinote (new features, bug fixes, style changes, feedback in general) you can join the discord.
License
This project is not affiliated with TriliumNext. It is a community-built iOS client.