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.
Configure the consent screen
Under APIs & Services → OAuth consent screen, set up the app name and support email and add these scopes:
| Scope | Used for |
|---|---|
openid, email, profile | Identifying the Google account being connected. |
https://www.googleapis.com/auth/calendar.readonly | Listing calendars and reading free/busy and event changes. |
https://www.googleapis.com/auth/calendar.events | Creating, 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:
https://cal.example.com/api/integrations/google/callback
https://cal.example.com/api/auth/callback/googleThe 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:
GOOGLE_CLIENT_ID=1234567890-abc123.apps.googleusercontent.com
GOOGLE_CLIENT_SECRET=GOCSPX-your-secret
APP_URL=https://cal.example.comAPP_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.
- Open Availability and click Connect Google Calendar.
- 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."
- 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:
| Column | What it does |
|---|---|
| Check conflicts | Busy times on this calendar block your availability. Turn it on for every calendar that represents your real schedule. |
| Add events | The 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
.icsattachment 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.
Google Meet links
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 Google | Result in Cauliflower |
|---|---|
| Event deleted | The 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 time | The 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 time | Ignored. |
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
| Symptom | Fix |
|---|---|
| "Google Calendar isn't configured" on Availability | GOOGLE_CLIENT_ID or GOOGLE_CLIENT_SECRET is missing. Set both and restart the server. |
Google shows redirect_uri_mismatch | The 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 attention | Google access was revoked or the refresh token expired. Choose Reconnect from the connection's menu. |
| Busy times aren't blocking slots | Check 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 meetings | Make 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 up | The 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.