README file from
GithubGoogle Calendar Tasks Sync
A gcal code block that turns into your Google Calendar (all calendars, month/week/day, create/edit/drag/delete), plus task notes mirrored to Google Tasks so they show up as checkboxes in the Google Calendar app on your phone.
- One code block, every calendar, each in its own Google colour.
- Create, edit, move, resize and delete events. Changes go straight to Google; Google is the source of truth for events.
- Task notes (
type: Taskfrontmatter) inside project folders are mirrored to a Google Tasks list per project. Title, due date and completion sync both ways; Obsidian is the source of truth for tasks. - Also available as its own pane: the ribbon icon or the Open calendar view command opens it in the right sidebar, and you can drag it into a tab.
- Log in once on desktop; phones and tablets reuse that login through your vault sync. No login screen on mobile. The login travels encrypted: each device unlocks it once with a passphrase you choose.
- Offline: the last synced state is drawn from a per-device cache.
Documentation
- Setup guide: first-time walkthrough from Google Cloud to your phone, with troubleshooting.
- Architecture: C4 context, containers and components, runtime flows, data, decisions.
- Changelog: what changed in each release.
Setup
You bring your own Google Cloud OAuth client, so nothing goes through a third-party server. The short version follows; the setup guide has every click.
-
In Google Cloud Console create or pick a project, then enable Google Calendar API and Google Tasks API.
-
Under Google Auth Platform, configure the consent screen (External), add yourself as a test user, and add the scopes
https://www.googleapis.com/auth/calendarandhttps://www.googleapis.com/auth/tasks. -
Create an OAuth client of type Desktop app. Copy its Client ID and Client secret.
-
In Obsidian → Settings → Google Calendar Tasks Sync, paste both values, set a Sync passphrase (it encrypts the login before it is written to disk) and click Log in to Google. A browser window opens; approve both permissions. The browser returns to
http://127.0.0.1:<port>and the plugin stores the encrypted refresh token. -
Put a code block in any note:
```gcal view: month ``` -
Publish the consent screen (Audience → Publish app). Refresh tokens issued while the app is in Testing expire after 7 days. Log in once more after publishing.
Code block options
| Option | Values | Default | Meaning |
|---|---|---|---|
view |
month, week, day, list |
month |
Initial view. Phones always open on the list |
height |
CSS length or auto |
auto |
Calendar height |
calendars |
list of calendar ids | [] |
Empty follows the toggles in settings; a list pins those calendars only |
tasks |
true, false |
true |
Draw task notes on their due date |
Task mirroring
Task notes live in <projects folder>/<project>/tasks/*.md (projects folder defaults to 10-projects) and need this frontmatter:
---
type: Task
title: Write the README
status: active # backlog | active | blocked | done
due: 2026-09-15 # optional, YYYY-MM-DD
google_task_id: # filled by the plugin
---
- Each project folder gets a Google Tasks list with the project's name.
status: done↔ completed. Any other status is needs action; completing a task on the phone sets the note todone, and un-completing adonenote sets it tobacklog.- Title, due date and completion merge field by field: a field changed on only one side since the last sync takes that side, so completing a task on the phone while another device moves its due date keeps both changes. A field changed on both sides goes to the newer one.
- A task added in a Google app appears as a new note in that project's
tasks/folder withstatus: backlog. Whatever you wrote in the task's notes stays there, below the link back to the note. - Deleting a note deletes the Google task. Deleting a task in Google (instead of completing it) makes the plugin re-create it from the note. Both wait about ten minutes first, because vault sync may still be delivering a rename or a deletion from another device.
- Moving a note to another project moves the task to that project's list.
- Only
title,due,statusandgoogle_task_idare ever written to a note; bodies and other properties are left alone. Google's task notes field starts with anobsidian://link back to the note.
Every device runs the mirror, so it is built for devices that see each other's notes a few minutes late:
- A task whose notes start with that
obsidian://link came from a note on some device. A device that does not have the note yet waits for vault sync and never imports the task as a second note. - When two notes claim one task (a sync conflict copy, or a note you duplicated), every device mirrors the same one, the note the task links back to. The others are listed in a notice: delete the copy, or clear its
google_task_idto make it a task of its own. Clear cacheempties the event cache only. The record of which note owns which task is kept.
Turn mirroring off in settings and the Google Tasks API is never called.
Display
Calendar colours follow the Google Calendar apps (the API reports the classic palette; the plugin maps it to the modern one you see on the web and phone) and are refreshed once an hour. Events that start at 00:00 are drawn without a time, like Google does; turn Hide midnight start times off in settings to show it.
Commands
Log in to Google (desktop only), Log out, Sync now, Clear cache, Open calendar view.
API for scripts and other plugins
app.plugins.plugins['google-cal-sync'].api opens the calendar's own event modal from anywhere, for example a dataviewjs block that lists today's events. Both calls resolve when the modal closes; saving or deleting there updates Google and the cache and redraws every calendar, exactly as a click in the calendar does.
const api = app.plugins.plugins['google-cal-sync'].api;
// Edit an event from this device's cache (local storage key `google-cal-sync:<calendarId>`).
await api.openEvent('primary', eventId);
// New event: all-day when `start` is left out; `end` defaults to an hour after `start`.
await api.createEvent({ date: '2026-10-15', start: '09:30', end: '10:30', calendarId: 'primary' });
openEvent rejects when the cache has no such event, and createEvent rejects a malformed date or time or an unknown calendar. A device that cannot show the calendar (no client, not logged in, locked) shows the same message in a notice and opens nothing. The types are in src/api.ts.
Mobile
Obsidian Mobile cannot run the local login server, so log in on desktop. The encrypted refresh token is stored in this plugin's data.json, which your vault sync (Obsidian Sync, iCloud, Syncthing, …) carries to the other devices. On each of them, enter the same sync passphrase once in the plugin settings and press Unlock; from then on the plugin refreshes the access token by itself. If a device shows "Log in on desktop", its data.json has not arrived yet; if it shows "Enter the sync passphrase", it has arrived and is waiting for the passphrase.
Network use and data storage (please read)
- The plugin talks only to Google:
accounts.google.comandoauth2.googleapis.comfor OAuth,www.googleapis.com/calendar/v3for events, andtasks.googleapis.com/tasks/v1for tasks. No other server, no telemetry. - Permissions requested from Google:
calendar(your calendars and their events) andtasks. The plugin itself only lists calendars and reads and writes events and tasks; it never calls the sharing, settings or calendar-deletion endpoints. - Your OAuth client ID and client secret are stored in plain text in
.obsidian/plugins/google-cal-sync/data.json. Google does not treat the secret of a desktop app as confidential, and both are needed on every device to refresh the access token. - The refresh token is stored encrypted in the same file, with a key derived from your sync passphrase (PBKDF2-SHA256, 600,000 iterations, AES-256-GCM). Your vault sync therefore carries only ciphertext. Each device keeps the passphrase in Obsidian's keychain (Settings → Keychain, encrypted by the operating system) and decrypts the token into memory only. A login stored by a version before 1.2.0 stays in plain text until you set a passphrase; the settings tab tells you so.
- Calendar events are cached per device in Obsidian's local storage (not in notes).
Clear cacheremoves it;Log outrevokes the token at Google and removes it too. If you suspect a copy ofdata.jsonleaked together with the passphrase, remove the app under Third-party apps & services in your Google account, which invalidates the token immediately. - Requests per sync: one per enabled calendar (incremental with Google's sync tokens) and one per project task list, plus one request per change you make.
Development
npm install
npm run dev # watch build to main.js
npm run build # type check + production build
npm run lint # eslint with eslint-plugin-obsidianmd
npm test # unit tests (node:test)
Symlink or copy the repository folder to <vault>/.obsidian/plugins/google-cal-sync/. FullCalendar (MIT) is bundled into main.js and loaded lazily when the first code block renders, so it does not add to app start time.
Compatibility
| Plugin | Obsidian |
|---|---|
| 1.x | 1.13.7+ |
License
MIT. FullCalendar (MIT) is bundled; see THIRD_PARTY_NOTICES.md.