A Cloudflare Worker that syncs your Outlook calendar to Google Calendar every minute using an ICS feed.
I got tired of manually checking two calendars and didn't want to pay $20 subscription for a subscription service to do it. This runs on Cloudflare's free tier and costs essentially nothing.
Read the full write-up on my blog: Calendar Sync
This could run on any serverless platform (Vercel, AWS Lambda, Deno Deploy, etc.) but I went with Cloudflare Workers.
- Fetches your Outlook calendar via its public ICS link
- Creates/updates/deletes events in a Google Calendar to match
- Runs every minute
- Tracks synced events so it won't duplicate or lose changes
- One-way sync only (Outlook → Google)
- No attendee info (Outlook's ICS feed strips this for privacy)
- Events sync within a window of -30 days to +1 year
- Go to Outlook → Settings → Calendar → Shared calendars
- Under "Publish a calendar", select your calendar and publish it
- Copy the ICS link
- Go to Google Cloud Console
- Create a project
- Enable the Google Calendar API
- Go to APIs & Services → OAuth consent screen
- Set up as External
- Add scope:
https://www.googleapis.com/auth/calendar - Add yourself as a test user
- Go to APIs & Services → Credentials
- Create an OAuth 2.0 Client ID (Web application)
- Add
http://localhost:3000/callbackas an authorized redirect URI - Copy the Client ID and Client Secret
- In Google Calendar, create a new calendar (e.g., "Work")
- Go to its settings → Integrate calendar → Copy the Calendar ID
# Clone and install
git clone <repo-url>
cd calendar-sync
npm install
# Add your credentials to .env
cp .env.example .env
# Edit .env with your values (ICS URL, Google Client ID, Secret, Calendar ID)
# Get your Google refresh token
npm run auth
# Add the refresh token to .env
# Generate a random token for SYNC_TOKEN (e.g., openssl rand -hex 32)
# Deploy secrets to Cloudflare
npx wrangler secret put OUTLOOK_ICS_URL
npx wrangler secret put GOOGLE_CLIENT_ID
npx wrangler secret put GOOGLE_CLIENT_SECRET
npx wrangler secret put GOOGLE_REFRESH_TOKEN
npx wrangler secret put GOOGLE_CALENDAR_ID
npx wrangler secret put SYNC_TOKEN
# Deploy
npm run deployTrigger a manual sync (token required):
curl "https://your-worker.workers.dev/sync?token=YOUR_SYNC_TOKEN"View real-time logs:
npx wrangler tailMIT