README file from
GithubTaskNotes CalDAV
Keep your TaskNotes tasks in sync with CalDAV task lists — Nextcloud Tasks, Apple Reminders, Radicale, Baikal, anything that stores VTODOs. Create a task in Obsidian and it shows up on your phone a couple of seconds later. Tick it off on the phone and the note updates on the next sync. Tags and projects decide which list a task lands in: #work to your Work list, everything in your House project to Home, everything else to a default list.
This is a companion plugin: it does nothing on its own and needs TaskNotes installed and enabled. Your tasks stay ordinary Markdown notes; the plugin only adds a few caldav_* keys to their frontmatter.
Why a separate plugin
The sync started life as a pull request to TaskNotes itself (#2280). TaskNotes has since grown an official runtime API for companion plugins, so the same mechanism now lives here and talks to TaskNotes only through that API. It works with stock TaskNotes and doesn't have to wait on a merge.
Requirements
- Obsidian 1.11.4 or newer. Passwords go into Obsidian's secret storage, which older versions don't have.
- TaskNotes with runtime API v1 (tested with TaskNotes 4.13.6). The plugin checks the API version and the capabilities it needs at startup, and tells you with a notice if something is missing.
- Tested on desktop against Nextcloud. The plugin isn't marked desktop-only, but it hasn't been tried on mobile yet.
Installation
The plugin is not in the community plugin directory (yet). Two ways to install it:
- BRAT: add
schobernoise/tasknotes-caldavin BRAT. - Manually: download
main.js,manifest.jsonandstyles.cssfrom the latest release into<vault>/.obsidian/plugins/tasknotes-caldav/, then enable TaskNotes CalDAV under Settings → Community plugins.
Setup
- Settings → TaskNotes CalDAV → Add account.
- Fill in Server URL, Username and Password. Any address on the server works; the plugin walks up to the account root on its own.
- Nextcloud:
https://cloud.example.com/remote.php/dav - iCloud:
https://caldav.icloud.comwith an app-specific password - Credentials are only ever sent over
https://(plainhttp://is allowed forlocalhost).
- Nextcloud:
- Under Task lists, press Discover. Event-only calendars, read-only subscriptions and deleted lists are filtered out.
- Route lists: pick a list under Route another list and give it tags, projects, or both. Under Everything else, choose the list for tasks that match none of them, or Don't sync. See Task lists, tags and projects.
- Optionally keep tasks out: Never sync tags, Only tasks in folders and Never sync tasks in folders. Subfolders count, and a task in a never-sync folder stays out even inside an only-these folder.
- Turn on Sync this account, then press Preview first sync. For each new list you get the numbers — to upload, to import, already matching, changed on both sides, moving in from other lists — and nothing is written until you confirm. A list only starts syncing after its first sync.
Two commands are available from the command palette: Sync tasks with CalDAV now and Unlink all tasks from CalDAV.
What syncs
| TaskNotes | CalDAV (VTODO) |
|---|---|
| Title | SUMMARY |
| Due / scheduled date | DUE / DTSTART |
| Status | STATUS |
| Priority | PRIORITY |
| Completed date | COMPLETED |
| Tags | CATEGORIES |
| Recurrence | RRULE |
| Projects (parent tasks) | RELATED-TO;RELTYPE=PARENT |
| Blocked by | RELATED-TO with the dependency type (RFC 9253) |
| Reminders | VALARM |
The note body is not synced. Anything the plugin doesn't model — a description written on the phone, attachments, custom X- properties — is left exactly as it was on the server.
Statuses. TaskNotes lets you define your own, CalDAV has a fixed set. A status marked completed becomes COMPLETED, one marked skipped becomes CANCELLED, everything else NEEDS-ACTION. Coming back, NEEDS-ACTION maps to your first open status by order.
Priorities. CalDAV has 1–9 plus 0 for not set, and task apps show them in three bands: 1–4 high, 5 medium, 6–9 low. By default your lowest-weight priority is sent as 9, the next as 5, and the rest are shared across 1–4, highest weight first; a priority with weight 0 is not set. With TaskNotes' own low/normal/high that is 9/5/1, what Apple Reminders uses. Under Priorities in the settings you can pick the number for each one. A number coming back that matches none of yours goes to the nearest priority in the same band, so a medium set on the phone never turns into a high one. Clearing the priority on the phone sets the note to your weight-0 priority.
The task tag. TaskNotes recognises task notes by a tag (#task by default). Every synced task would carry it, so it's left off the server unless you turn on Sync the task tag. Your notes always keep it, even when a phone app edits or drops a task's categories.
Subtasks. A subtask is a task whose Projects field links to another task, and it arrives on the server as a real subtask. A link is only sent once both tasks exist on the server; projects that are plain notes rather than tasks are left out.
Task lists, tags and projects
An account is one server login; it can sync any number of its lists. Each list gets tags, projects, or both, and the rows are tried top to bottom:
- A new task goes to the first list whose tags it has (nested tags count:
workalso matcheswork/client) or whose projects it belongs to, otherwise to the Everything else list, otherwise nowhere. - Belonging to a project means linking it in the task's Projects field, or being a subtask of a task that does, however deep. A whole subtask tree lands in one list, which matters because task apps only nest subtasks within a list. Add a project by typing its note's name; renaming or moving the note later keeps the route.
- Changing a task's tags moves it: tag a Home task
#workand it is deleted from Home and created in Work, with the same UID and anything a phone app added (a description, say) carried over. A task stays put as long as it still has one of its list's tags, so a Work task that also gets#homestays in Work. Losing its list's tag sends it to Everything else; with nowhere to go, it stays where it is. - Moving a task between lists on the phone swaps its tags: moved from Work to Home, the note loses
#workand gains#home. It is not mistaken for a deletion. Project lists work the same way: moved out, the task loses its link to that list's project; moved in, it gets a link to the list's first project, unless it already belongs to one of them. Links to other projects stay. - A task created in a list on the phone arrives with that list's first tag, or a link to its first project when the list has no tags.
- The routing tag stays off the server. Every task in Work would carry
work, so it's hidden like the task tag, and a phone app editing categories can't remove it from the note. - Editing a list's tags in settings moves the affected tasks on the next sync. Removing a list moves its tasks where their tags now point; a task with nowhere to go is unlinked, and its copy stays on the server.
- Never sync tags and Only tasks in folders only decide which tasks get picked up. A task that's already synced keeps syncing when it gains a Never sync tag or leaves those folders; unlink it to stop.
- A never-sync folder takes tasks out of sync. Move a synced note into one and, on its next push or sync, its copy is deleted from the server and its
caldav_*keys are removed from the note. Nothing else in the note changes. The server copy has to go, because a copy nothing links to would come back as a new note on the next poll.
Upgrading from 0.4: Only tasks in folder becomes a one-entry folder list, and lists route by no project until you add one. If the default priority numbers changed for you, the first sync after the upgrade sends every affected task once more with its new number. Nothing in your notes changes.
Upgrading from 0.3: an account's list becomes its only list. An include tag filter becomes that list's tags; otherwise the list takes Everything else, and an exclude filter becomes Never sync. Nothing in your notes changes.
How it behaves
- Timing. Local edits are pushed about 1.5 seconds after you stop typing, whether you edit through TaskNotes or type straight into the frontmatter. Server changes are polled per account (every 15 minutes by default), and a poll that finds the list unchanged stops after a single request.
- Conflicts. If both sides changed since the last sync, the server rejects the write (ETag mismatch) and the more recently changed side wins. This compares your computer's clock with the server's, so both should be roughly right.
- Deleting. Deleting a note deletes the task on the server. When a task disappears from the server you choose per account: archive the note (default), keep it and stop syncing, or delete it.
- Dates the server can't take as-is. CalDAV requires start and due to be the same kind (both dates or both date-times) and due not to come before start; TaskNotes allows both. On the server, a plain date next to a timed one gets a time (start 00:00, due 23:59), and a start after the due date is left out. Your notes keep their own values, and a sync only writes a field back into a note when the server actually changed it.
- One bad task doesn't block the rest. A task the server rejects is skipped, the rest of the sync carries on, and a notice names the task and the server's reason. It's retried on the next sync.
- Archived tasks are never uploaded.
- Several accounts. A task already synced stays with its account; a new one goes to the first enabled account that routes it somewhere, so it's never uploaded twice.
- Offline. A push that fails is queued and retried every minute, up to five attempts.
- Unlinking removes the
caldav_*keys and deletes nothing on either side. The link is also what stops a task being uploaded twice, so syncing the same list again afterwards gives you a second copy of every task.
Known limitations
- If TaskNotes stores titles in filenames (its default), characters that can't go in a filename, like
:, are dropped from titles pulled in from the server. The server keeps its version; the plugin doesn't push the shortened title back. - An
IN-PROCESSstatus from the server can't be told apart from "not started" unless your statuses make it obvious. The plugin picks your second open status. - If you disable TaskNotes while this plugin is running, sync stops. Reload this plugin after re-enabling TaskNotes.
- When a parent task moves to another project, its subtasks follow on the next sync that finds something changed on the server (or on Sync tasks with CalDAV now), not right away, because their own notes didn't change.
Privacy
Your password lives in Obsidian's secret storage and is never written to data.json. Task UIDs are random, so vault paths are never visible to anyone else who can see the list. The plugin talks to the servers you configure and nothing else.
Development
npm install
npm run dev # esbuild watch, writes main.js
npm run build # typecheck + production bundle
npm test # unit tests for the pure CalDAV modules (Jest, jsdom)
src/main.ts lifecycle: connect to TaskNotes, wire events and commands
src/tasknotes.ts the slice of the TaskNotes runtime API used, plus the version/capability check
src/CalDavSyncService.ts push, pull, conflicts, relations, retry queue
src/SettingsTab.ts account settings UI
src/settings.ts settings types and defaults
src/caldav/ pure modules: CalDAV client, XML, ICS dates, VTODO mapping, tag and project routing, reconciliation
tests/caldav/ unit tests for src/caldav/
The pure modules in src/caldav/ hold every sync decision (what a VTODO looks like, which list a task belongs to, who wins a conflict, what to upload) and do no I/O, which is why they're the part with unit tests. CalDavSyncService carries out those decisions: vault writes, timers, network.
Releasing
npm version patch # or minor / major: runs tests + build, bumps manifest.json and versions.json, commits, tags
npm run release # pushes main and the tag to origin, Codeberg and GitHub
Pushing the tag triggers CI on GitHub and Codeberg, which builds the plugin and attaches main.js, manifest.json and styles.css to a release. Tags carry no v prefix, because Obsidian looks releases up by the exact version in manifest.json.
Credits
Built on TaskNotes by Callum Alpass and its runtime API. MIT licensed, see LICENSE.