Sync Oura Ring data to Google Calendar using Google Apps Script.
- Sleep data - Duration, start/end time, sleep stages, vitals, scores
- Workout data - Activity type, duration, calories, distance
- Daily scores - Readiness, Sleep Score, Stress, Activity, SpO2, Sleep Time Recommendation (combined in one all-day event)
- Rest Mode - Multi-day all-day event when Rest Mode is enabled
- Title:
Sleep 7h 30m (85) - Time: 23:30 - 07:00
- Color: Blue
- Title:
Running - 450 kcal - Time: 18:00 - 19:00
- Color: Orange
- Title:
Oura: R:82 | S:85 | St:restored - Color: Green
- Title:
Rest Mode - Duration: Start day to end day
- Color: Purple (Grape)
- Node.js (v18+)
- Google Account
- Oura Ring with API access (Gen3 or Ring 4)
- Active Oura membership
- Clone this repository
git clone https://github.com/tai-sho/oura-google-calendar-sync.git
cd oura-google-calendar-sync
npm install- Login to clasp
npx clasp login- Create a new GAS project
npx clasp create --title "oura-google-calendar-sync" --type standaloneOr use existing project:
cp .clasp.json.example .clasp.json
# Edit .clasp.json and set your scriptId- Push to GAS
npm run push- Go to Oura Developer Portal
- Click "New Application"
- Fill in the form:
- App Name: Any name (e.g., "My Calendar Sync")
- Redirect URI:
https://script.google.com/macros/d/{SCRIPT_ID}/usercallback- Replace
{SCRIPT_ID}with your GAS script ID (found in.clasp.json)
- Replace
- Copy the Client ID and Client Secret
In GAS editor: Project Settings > Script Properties
| Property | Required | Description |
|---|---|---|
OURA_CLIENT_ID |
Yes | Your Oura OAuth Client ID |
OURA_CLIENT_SECRET |
Yes | Your Oura OAuth Client Secret |
CALENDAR_ID |
Yes | Target Google Calendar ID |
SYNC_DAYS |
No | Number of days to sync (default: 7) |
- For primary calendar: Use your Google account email address
- For other calendars: Open Google Calendar > Calendar Settings > Calendar ID
- In GAS editor, run
authorizeOura() - Check the execution log for the authorization URL
- Open the URL in your browser
- Log in to Oura and click "Allow"
- You should see "Authorization successful!"
In GAS editor, run any of these functions:
syncOuraToCalendar()- Sync all data (sleep, workouts, daily scores, rest mode)syncSleep()- Sync only sleep datasyncWorkouts()- Sync only workout datasyncDailyScores()- Sync only daily scoressyncRestMode()- Sync only rest mode periods
testOuraConnection()- Verify Oura OAuth is workingtestCalendarConnection()- Verify Calendar ID is correctcheckOuraAuth()- Check authorization status
authorizeOura()- Start authorization flowlogoutOura()- Reset authorization (re-authorize required)
Run setupDailyTrigger() to set up automatic daily sync at 8:00 AM.
To remove the trigger, run removeTriggers().
npm run push # Push local files to GAS
npm run pull # Pull from GAS to local
npm run watch # Watch and auto-push changes
npm run open # Open GAS editor in browser
npm run logs # View execution logssrc/
├── appsscript.json # GAS manifest
├── main.gs # Entry points, OAuth, and triggers
├── config.gs # Configuration management
├── ouraApi.gs # Oura API client
├── calendarService.gs # Google Calendar operations
├── syncService.gs # Sync logic
├── formatters.gs # Event formatting
└── utils.gs # Utility functions
Set the OURA_CLIENT_ID and OURA_CLIENT_SECRET in Script Properties.
Run authorizeOura() and complete the authorization flow.
Check that the CALENDAR_ID is correct. For primary calendar, use your email address.
Your OAuth token may have expired. Run logoutOura() then authorizeOura() to re-authorize.
SpO2 data requires an active Oura membership and may not be available for all users.
MIT