Sync your Apple Music listening history to Google Calendar—automatically.
songcal is a web application that connects your Apple Music and Google Calendar accounts. Every song you play is automatically added to a dedicated calendar as an event, giving you a visual history of your music listening.
- Framework: TanStack Start
- Runtime: Cloudflare Workers
- Database: Cloudflare D1
- Auth: better-auth with Google OAuth
- Background Jobs: Cloudflare Cron Triggers + Queues
- Styling: Tailwind CSS
- Sign in with Google (also grants Calendar access)
- Connect Apple Music via MusicKit JS
- Automatic sync every 1 minute
- Calendar events with song details and Apple Music links
- Multi-user support
- Bun runtime
- Wrangler CLI
- Cloudflare account
- Apple Developer account with MusicKit enabled
- Google Cloud project with Calendar API enabled
git clone https://github.com/851-labs/songcal.git
cd songcal
bun install- Go to Apple Developer > Keys
- Create a new key with MusicKit enabled
- Download the
.p8file - Note your Key ID and Team ID
- Go to Google Cloud Console
- Create or select a project
- Enable the Google Calendar API
- Create OAuth 2.0 credentials (Web application type)
- Add authorized redirect URIs:
http://localhost:3000/api/auth/callback/google(development)https://your-domain.com/api/auth/callback/google(production)
- Note your Client ID and Client Secret
# Login to Cloudflare
wrangler login
# Create D1 database
wrangler d1 create songcal
# Create Queue
wrangler queues create songcal-syncUpdate wrangler.jsonc with your D1 database ID from the create command output.
cp env.example .envEdit .env with your credentials:
BETTER_AUTH_SECRET="generate-a-random-32-char-secret"
GOOGLE_CLIENT_ID="your-client-id.apps.googleusercontent.com"
GOOGLE_CLIENT_SECRET="GOCSPX-your-secret"
APPLE_TEAM_ID="XXXXXXXXXX"
APPLE_KEY_ID="XXXXXXXXXX"
APPLE_PRIVATE_KEY="-----BEGIN PRIVATE KEY-----
...your key content...
-----END PRIVATE KEY-----"# Generate migrations from schema
bun run db:migrations:generate
# Apply migrations locally
bun run db:migrations:applybun run devwrangler secret put BETTER_AUTH_SECRET
wrangler secret put GOOGLE_CLIENT_ID
wrangler secret put GOOGLE_CLIENT_SECRET
wrangler secret put APPLE_TEAM_ID
wrangler secret put APPLE_KEY_ID
wrangler secret put APPLE_PRIVATE_KEYEdit wrangler.jsonc and update BETTER_AUTH_URL to your production domain.
bun run db:migrations:apply:prodbun run deploy| Command | Description |
|---|---|
bun dev |
Start development server |
bun run build |
Build for production |
bun run deploy |
Build and deploy to Cloudflare |
bun types:check |
TypeScript check |
bun types:generate |
Generate Cloudflare Worker types |
bun db:migrations:generate |
Generate migrations from schema |
bun db:migrations:list |
List migrations (local) |
bun db:migrations:apply |
Apply migrations (local) |
- Apple Music API returns only the last 10 tracks with no timestamps
- Timestamps reflect when tracks were detected, not exact play time
- Sync frequency: every 1 minute
MIT