Skip to content

Google Calendar

Google Calendar is a two-way provider implemented in apps/api/src/sync/adapters/google.ts. One Musubi user can connect multiple Google accounts; every imported calendar retains the account that owns its provider credentials.

Read Sync engine architecture first if you are changing adapter code. This page focuses on Google-specific behavior.

  1. Create Google OAuth clients.

    The API needs a Web client. Native sign-in also needs platform-facing client identifiers compiled into the app.

  2. Set the server variables.

    GOOGLE_WEB_CLIENT_ID=...
    GOOGLE_CLIENT_SECRET=...

    Better Auth requests offline access and explicit consent so Google returns a refresh token. Calendar read/write scope is required.

  3. Set the native build variables when testing the mobile flow.

    EXPO_PUBLIC_GOOGLE_WEB_CLIENT_ID=...
    EXPO_PUBLIC_GOOGLE_IOS_CLIENT_ID=...

    Rebuild the development client after changing EXPO_PUBLIC_*; Metro cannot replace values compiled into native configuration.

  4. Confirm capability discovery.

    Terminal window
    curl http://localhost:7531/api/v1/server

    syncProviders should contain google. Then connect the account in Settings and inspect API logs using the response x-request-id.

See Environment variables and Authentication for the full native and Better Auth setup.

  • Calendars come from users/me/calendarList.
  • A calendar is read-only unless Google reports owner or writer.
  • Color is user-specific and comes from the calendarList entry’s backgroundColor.
  • Renaming or recoloring a linked calendar patches Google before Musubi updates its local mirror.

Google provides an unbounded syncToken:

  1. the first pull paginates through the full event history;
  2. the final page returns nextSyncToken;
  3. later pulls send that token and receive only changes;
  4. 410 Gone means the cursor expired, so the adapter performs a reset pull and the engine reconciles the full result.

Persist the next cursor only after a successful pull. A partially consumed page sequence must never advance it.

Google shape Musubi behavior
start.date All-day event at UTC midnight
Exclusive end.date Subtract one day for Musubi’s inclusive all-day end
Recurrence array Preserve RRULE/EXDATE text on the series master
Cancelled item Tombstone the mirrored event
Conference entry point Store as the event URL
htmlLink Deliberately ignored; it is a Google UI link, not the meeting

sanitizeRecurrence() repairs provider quirks seen in received recurrence rules. toGoogleRecurrence() keeps all-day EXDATE and UNTIL values DATE-typed when pushing them back.

A revoked or unusable refresh token sets the account sync status to a reconnect state. Reconnect that exact account; do not delete other accounts belonging to the same provider.

Look for a Google 410, failed pagination, or a cursor that was not persisted. A legitimate expired token causes one reset. Repeated resets indicate cursor storage or token-refresh trouble.

Inspect accessRole in the Google Calendar List response. Subscribed holiday calendars and calendars shared as reader are intentionally not writable.

Check GOOGLE_WEB_CLIENT_ID and GOOGLE_CLIENT_SECRET, restart the API, then inspect /api/v1/server. Native values alone do not enable server-side sync.

When changing this adapter:

  • preserve pagination and cursor atomicity;
  • cover all-day inclusive/exclusive conversion in both directions;
  • test recurring master, EXDATE, cancelled item, and 410 reset behavior;
  • keep calendar-list colors separate from calendar resources;
  • use the shared OAuth refresh/encryption path;
  • record provider failures with bounded metric labels; and
  • run pnpm check (including the fake Google 410/pagination server), pnpm test:provider-db when touching OAuth lifecycle, then a real sandbox-account round trip when credentials are available.