Integrations / Google Calendar

Integrations

Google Calendar

Free/busy, Google Meet links and two-way event sync.

Google Calendar connects each teammate's real schedule to Cauliflower. Once connected, busy times on their calendars block availability, every booked meeting becomes a Google Calendar event (with a Google Meet link when the meeting type uses Meet), and the two stay in sync in both directions.

Setup has two parts: a server admin creates one OAuth client for your Cauliflower installation, then every teammate connects their own Google account.

Create the OAuth client

You need a Google Cloud project you control. One OAuth client serves the whole installation, and it also enables "Sign in with Google".

Enable the Calendar API

In the Google Cloud Console, select or create a project, open APIs & Services → Library and enable the Google Calendar API.

Under APIs & Services → OAuth consent screen, set up the app name and support email and add these scopes:

ScopeUsed for
openid, email, profileIdentifying the Google account being connected.
https://www.googleapis.com/auth/calendar.readonlyListing calendars and reading free/busy and event changes.
https://www.googleapis.com/auth/calendar.eventsCreating, moving and deleting meeting events.

If everyone who connects is in your Google Workspace organization, choose the Internal user type.

Create the client

Under APIs & Services → Credentials, create an OAuth client ID of type Web application and add both redirect URIs, using your APP_URL:

Text
https://cal.example.com/api/integrations/google/callback
https://cal.example.com/api/auth/callback/google

The first is used when teammates connect calendars, the second for "Sign in with Google". The Integrations page shows both URIs with copy buttons until Google is configured.

Set the environment variables

Add the client's credentials to your server's environment and restart Cauliflower:

.env
GOOGLE_CLIENT_ID=1234567890-abc123.apps.googleusercontent.com
GOOGLE_CLIENT_SECRET=GOCSPX-your-secret
APP_URL=https://cal.example.com

APP_URL must be the public HTTPS address people use, because redirect URIs are built from it. See Configuration.

Publish the consent screen

Google expires refresh tokens after 7 days for apps with an External user type that are still in Testing. Publish the app, or use the Internal user type, so connections don't stop working after a week.

Connect your calendar

Each teammate connects their own Google account. Admins can view a teammate's availability, but only the account owner can connect or change their calendars.

  1. Open Availability and click Connect Google Calendar.
  2. Choose your Google account and allow calendar access on the consent screen. Leaving the calendar permissions unchecked fails with "Please allow calendar access when connecting Google."
  3. You're returned to Availability with "Google Calendar connected" and a list of your calendars.

Use Connect another account to add more Google accounts, for example a personal one whose events should block time. Each connection has a menu with Refresh calendar list, Reconnect and Disconnect.

Credentials are encrypted at rest. Cauliflower requests offline access so it can keep checking your calendar after you close the browser.

Choose calendars

Every calendar on a connected account is listed with two controls:

ColumnWhat it does
Check conflictsBusy times on this calendar block your availability. Turn it on for every calendar that represents your real schedule.
Add eventsThe destination calendar where new meetings are created. You have exactly one across all connected accounts. Read-only calendars can't be selected.

When you first connect, your primary calendar is checked for conflicts and, if you don't have one yet, becomes the destination calendar. Other calendars start unchecked.

Cauliflower only reads free/busy information for conflict checks, not event titles or attendees. Events marked as "Free" in Google don't block time. Calendars added or removed in Google show up after Refresh calendar list.

Meetings in Google Calendar

When a meeting is booked, Cauliflower creates an event on the host's destination calendar:

  • Title and description come from the meeting type's invite templates.
  • Attendees are the invitee, their guests and any co-hosts (collective hosts or required Handoff teammates).
  • Invitations are sent by Google to every attendee. Cauliflower's own confirmation emails skip the .ics attachment in this case so people don't get two invites.
  • Attendees can't edit the event. The description contains the reschedule and cancel link.

If event creation fails, the booking is still saved. The meeting page shows the error and a Retry calendar sync action.

When a meeting type's location is Google Meet, the event is created with a Meet conference and the link is saved on the meeting. It appears on the confirmation page, in emails, in the {{location}} tag and in webhook payloads. The host must have a destination calendar connected; without one, the meeting is booked but has no Meet link.

Two-way sync

Changes flow in both directions.

From Cauliflower to Google. When a meeting is rescheduled or cancelled, whether from the app, the invitee's manage page or the API, Cauliflower moves or deletes the event and Google notifies the attendees.

From Google to Cauliflower. Hosts can manage meetings straight from their calendar:

Change in GoogleResult in Cauliflower
Event deletedThe meeting is cancelled by the host with the reason "Cancelled in Google Calendar". meeting.cancelled fires and the CRM is updated. Cauliflower doesn't email attendees again, because Google already did.
Event moved to a new timeThe meeting is rescheduled to the new time without an availability check, since the host chose it. Reminders move, meeting.rescheduled fires and the CRM is updated.
Event changed to all-day, or edited without changing the timeIgnored.

Calendars that hold upcoming Cauliflower meetings are scanned for changes about every two minutes. Sync runs in Cauliflower's background worker, so it needs INTERNAL_CRON=true (the default in Docker) or a scheduler calling /api/cron. See Deploying.

Troubleshooting

SymptomFix
"Google Calendar isn't configured" on AvailabilityGOOGLE_CLIENT_ID or GOOGLE_CLIENT_SECRET is missing. Set both and restart the server.
Google shows redirect_uri_mismatchThe redirect URI in Google Cloud must match APP_URL exactly, including https and without a trailing slash.
"Please allow calendar access when connecting Google."The calendar checkboxes were left unticked on the consent screen. Connect again and allow access.
"The connection request expired. Please try again."The consent screen was open for more than 10 minutes, or you signed in as a different Cauliflower user in between. Start again from Availability.
A connection shows Needs attentionGoogle access was revoked or the refresh token expired. Choose Reconnect from the connection's menu.
Busy times aren't blocking slotsCheck that Check conflicts is on for that calendar and that the event isn't marked "Free". Results can be up to 45 seconds old while browsing; bookings always re-check live.
No event or Meet link on new meetingsMake sure a writable calendar is selected under Add events and the connection is active, then use Retry calendar sync on the meeting.
Changes made in Google aren't picked upThe background worker isn't running. Check INTERNAL_CRON or your /api/cron schedule.

If Google can't be reached when slots are calculated, the error is logged and that calendar's busy times are skipped rather than blocking all booking, so keep an eye on connections that need attention.