Sync Google Calendar
The Google Calendar connector is an experimental / preview feature. Validate it against your own account before relying on it in production. It is read-only — events are brought in as nodes; nothing is written back to Google Calendar.
This guide connects a Google account and mounts a calendar so its events appear
as raisin:Event nodes, kept current on an interval within a moving time window.
Then you query them with ordinary SQL. For the concepts, see
Virtual Nodes.
What you'll build
Before you start
- A running RaisinDB instance and a repository you can install packages into.
RAISIN_MASTER_KEYset and backed up on the server — Google tokens are stored AES-256-GCM encrypted and decrypted by the engine before each call.- A Google account with the calendar you want to mount.
Step 1 — Install the connector
The Google Calendar connector ships as a built-in package:
raisindb package install google-calendar-adapter --repo myapp
This deploys the Calendar v3 adapter (/adapters/google-calendar), a default
mapper (/mappers/google-calendar-default), and a disabled connector template
(/integrations/google-calendar). No credentials are shipped.
Step 2 — Create a Google OAuth client and connect
In the Google Cloud Console:
- Create or select a project and enable the Google Calendar API.
- Configure the OAuth consent screen and add the scope
https://www.googleapis.com/auth/calendar.readonly(the least-privilege scope the connector requests). - Create an OAuth 2.0 Client ID of type Web application.
- Add the redirect URI
https://<your-host>/api/integrations/<repo>/oauth/callback(repo-scoped). - Copy the Client ID and Client secret.
Don't hand-assemble the redirect URI. Open Connectors → Google Calendar in
the admin console — the Redirect URI is shown there read-only with a copy
button (https://<your-host>/api/integrations/<repo>/oauth/callback); paste that
exact value into step 4. If you later switch the mount to webhook/hybrid, the
per-mount Notification URL is shown on the mount (Google Calendar
auto-registers it — see Real-time sync). And if you
are authoring your own connector, put the provider-side steps into the
connector's setup_instructions (Markdown) and an optional docs_url — the admin
console renders them on the connector page.
Then in the admin console, open Connectors → Google Calendar:
- Paste the Client ID and Client secret (the secret is encrypted at rest
into
client_secret_encrypted, never stored in cleartext). - Set the redirect URI to match step 4, then enable the connector.
- Click Connect an account and complete Google's OAuth flow. The account is
stored with encrypted tokens;
access_type=offlineyields a refresh token so the engine keeps the mount synced without re-consent.
Use Test connection before mounting — it runs capabilities and a small
list probe against your calendar.
Step 3 — Mount a calendar
Create a raisin:VirtualMount. remote_root is the calendar id (primary for
your default calendar); sync_config.window bounds how far back and ahead events
are synced.
node_type: raisin:VirtualMount
properties:
title: My Calendar
integration_ref: /integrations/google-calendar
account_ref: "<connected account id from step 2>"
target_workspace: default
target_branch: main
mount_path: /calendar
remote_root: primary # calendar id (defaults to "primary")
sync_config:
mode: poll
interval_seconds: 300
ephemeral: false # persistent — keep the calendar in sync
window:
days_back: 7 # default 7
days_ahead: 90 # default 90
enabled: true
The engine runs a windowed full reconcile first, then switches to incremental
syncToken deltas. Each event becomes a raisin:Event node with typed
properties: title, start, end, all_day, location, attendees,
organizer, recurrence, status, url, and calendar_id — plus the reserved
__virtual / __mount_id / __external_id / __synced_at metadata. Cancelled
events are treated as deletions and are not materialized.
Step 4 — Query events with SQL
Synced events go through the normal write path, so they are just rows. start and
end are stored as ISO-8601 strings, which sort correctly as text — so "upcoming
events" is a straight string comparison against the current time:
SELECT properties->>'title'::String AS title,
properties->>'start'::String AS starts_at,
properties->>'location'::String AS location
FROM 'default'
WHERE node_type = 'raisin:Event'
AND properties->>'start'::String > now()
ORDER BY properties->>'start'::String
LIMIT 20;
now() returns the current timestamp as an ISO string, so this returns the next
20 events across the mounted calendar. Add AND properties->>'__mount_id'::String = '<mount node id>' to scope to one specific calendar mount.
Running in production
- Auth expiry pauses the mount. On
401/403the adapter throwsauth_expired; the engine refreshes the token or sets the mountauth_required. On429it throwsrate_limitedand backs off. - Multi-node clusters need the Redis locks backend.
RAISIN_MASTER_KEYmust be set and backed up.- The window moves with time. Because bounds are computed as
now ± window, events drop off the back as they age out and appear at the front as they enter the look-ahead — the mount is a rolling window, not a full archive.
Next steps
- Connect Microsoft 365 — the same
raisin:Eventmodel for Outlook calendars. - Connect Gmail — mailboxes as ephemeral nodes.
- Adapter reference — full field tables.