A native iOS client for self-hosted TriliumNext note-taking servers.
- 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
- iOS 18.0+ (the offline cache uses SwiftData indexes, which need iOS 18)
- Xcode 16.0+
- Swift 5.9+
- A self-hosted TriliumNext server (v0.95.x – v0.106.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)
This project uses XcodeGen to generate the .xcodeproj:
brew install xcodegen
cd Trinote
xcodegen generateopen Trinote.xcodeproj- Open the project settings
- Select the Trinote target
- Under Signing & Capabilities, set your development team
- Adjust the bundle identifier if needed
Select an iOS 18.0+ simulator or device and press Cmd+R.
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.shIf you edit project.yml without running xcodegen generate, the next build may overwrite your YAML changes with the values still in the .xcodeproj.
- 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
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.
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.
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
- 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
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)
- 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.
This project is not affiliated with TriliumNext. It is a community-built iOS client.