Google Calendar
calrs can connect to Google Calendar as a CalDAV source using Google’s OAuth2 flow. Unlike a username/password source, Google requires you (the calrs operator) to register an OAuth2 application in Google Cloud and configure its client ID and secret in calrs once. After that, individual users connect their own Google accounts through the dashboard.
This page walks through the Google Cloud setup. The values you collect at the end go into Admin → Auth → Google OAuth2 in the calrs dashboard.
1. Pick (or create) a Google Cloud project
Go to the Google Cloud Console and either select an existing project or create a new one (e.g. calrs-prod). Everything below is scoped to this project.
The project is just a container for the OAuth2 client and the enabled APIs. You can use the same project for staging and production, or split them. It makes no functional difference to calrs.
2. Enable the required APIs
In APIs & Services → Library, enable:
- CalDAV API: used for CalDAV access to the user’s calendars (reading busy times and writing bookings back). It is a separate entry in the Library from the Calendar API; search for “CalDAV API”.
- Google Calendar API: used to attach Google Meet links to bookings (see Google Meet auto-links). Enable it even if you do not use Meet today, so the option works the day an event type switches to it.
- OIDC userinfo: Google exposes this automatically when you request the
openid emailscopes; there is no separate “API” to toggle, but the OAuth consent screen must allow those scopes (see step 5).
Without the CalDAV API enabled, every CalDAV request will fail with a 403 even though the OAuth2 handshake itself succeeds; enabling only the Google Calendar API is not enough. Without the Google Calendar API, bookings still sync and write back, but Google Meet links cannot be generated.
3. Create the OAuth2 client
In APIs & Services → Credentials, click Create credentials → OAuth client ID.
- Application type: Web application
- Name: anything (e.g.
calrs)
You will get a client ID and client secret. Both are stored encrypted in calrs (auth_config.google_oauth2_client_id / google_oauth2_client_secret) once you paste them into the admin panel.
4. Authorized redirect URI
Under Authorized redirect URIs, add exactly one entry:
{CALRS_BASE_URL}/dashboard/sources/google/callback
Replace {CALRS_BASE_URL} with the public URL of your calrs instance, e.g.:
https://cal.example.com/dashboard/sources/google/callback
Notes:
- The URI must match byte-for-byte. No trailing slash, correct scheme (
https://in production), correct host. Google rejects the callback otherwise. CALRS_BASE_URLis the same env var calrs uses for OIDC redirects and email links. Keep them consistent.- If you run multiple environments (staging + prod), either register the same client with multiple redirect URIs or create one OAuth2 client per environment.
You do not need to set “Authorized JavaScript origins”; calrs performs the redirect server-side.
5. Scopes calrs requests
When a user connects their Google account, calrs requests these scopes:
| Scope | Why |
|---|---|
https://www.googleapis.com/auth/calendar | Full read/write access to the user’s calendars via Google’s CalDAV endpoint. Needed both to read busy times and to push confirmed bookings back to the calendar. |
openid email | OpenID Connect userinfo, used once at connect time to discover the account’s email address. Google’s CalDAV principal URL is /caldav/v2/{userEmail}/user, so calrs needs to know which email to scope the source to. |
calrs also passes access_type=offline and prompt=consent so that Google issues a long-lived refresh token. The refresh token is stored encrypted and used to mint new access tokens as needed (existing tokens are rotated automatically).
On the OAuth consent screen configuration (APIs & Services → OAuth consent screen), add the Calendar scope explicitly. The openid and email scopes are listed under the “non-sensitive” defaults and don’t need additional review.
The same Calendar scope is enough to attach a Google Meet conference to a write-back event. No extra Meet scope, and users who already connected Google Calendar do not need to reconnect.
6. Test users vs. publishing the consent screen
While the OAuth consent screen is in Testing status, only accounts explicitly listed under Test users can complete the OAuth flow. Everyone else gets Error 403: access_denied at the Google consent page.
You have two options:
- Keep it in Testing if calrs is only used by a small, known group (say, a single team or family). Add each user’s Google email to the Test users list. Refresh tokens issued to test users expire after 7 days, so users will need to reconnect their source weekly.
- Publish the app (button on the OAuth consent screen page) for any larger or longer-running deployment. Because the Calendar scope is marked sensitive/restricted by Google, publishing triggers Google’s app verification process. They will ask for a homepage, privacy policy, branding assets, and (for restricted scopes) a security assessment. This can take weeks. Until verification completes, users see an “unverified app” warning but can still proceed via Advanced → Go to {app} (unsafe).
For a self-hosted instance used by you and a handful of people, the Testing mode + Test users approach is usually fine; just remember the 7-day refresh token expiry.
Google Meet auto-links
When an event type’s location is Google Meet (auto-generated link), confirmed bookings attempt to attach a unique Meet conference owned by the host, as long as that host still has Google Calendar connected with write-back:
- calrs writes the booking to the host’s Google write-back calendar (same CalDAV PUT as any other booking).
- It then calls Google Calendar API
events.patchwithconferenceData.createRequest(hangoutsMeet) using the host’s existing OAuth token. - The Meet URL is stored on
bookings.meeting_urland included in confirmation emails, the guest ICS, and CalDAV copies on any other calendars.
No extra OAuth scope or admin field is required beyond the setup above, as long as the Google Calendar API was enabled in step 2. Users who already connected Google Calendar do not reconnect.
Who owns the Meet
| Booking | Meet owner |
|---|---|
| Personal event type | The event type owner |
| Team round-robin | The assigned member |
| Team collective | The first eligible member by name when the booking was made (same person as the calendar ORGANIZER, kept even if the team changes later) |
Dynamic group (/u/alice+bob/...) | The event type owner (first username). Checked on the slots page so the guest is not asked to fill the form first. Other participants do not need Google connected. |
The conference is created with that person’s token on their write-back calendar, so they see the native “Join with Google Meet” button in Google Calendar.
Save-time gate
You cannot save an event type as Google Meet unless every scheduling-relevant host already has Google Calendar connected and has picked a write-back calendar:
- Personal: you
- Team: every eligible member (enabled, non-zero weight)
The form lists anyone still missing. If someone later disconnects Google, the booking still confirms; confirmation and email show a “Google Meet” label if minting failed (the Meet URL is omitted and a warning is logged, same as a failed meeting webhook).
Reschedule and cancel
Reschedule keeps the same Meet URL and patches only the event times via Calendar API (a second CalDAV PUT would strip the conference). Cancel still deletes {uid}.ics over CalDAV, which removes the Google event and its Meet.
That time patch is the host’s only copy of the booking, so it is retried three times a second apart. If it still fails, the host is emailed: the booking, the guest’s invite and the reminders hold the new time while their Google Calendar holds the old one, and they need to move the event by hand. Move it rather than recreating it, or the Meet link the guest already has stops admitting anyone.