RockList.Live is a Twitch song request app for music and Rocksmith streams. Viewers browse public playlists, sign in with Twitch, and add or manage their own requests from channel pages. Streamers and moderators manage request rules, queue behavior, VIP token rewards, overlays, and channel access from the dashboard and Twitch panel.
It runs on TanStack Start, Cloudflare Workers, D1, Durable Objects, Queues, KV, and TypeScript.
- CONTRIBUTING.md
- docs/local-development.md
- docs/deployment-workflow.md
- docs/bot-operations.md
- docs/README.md
- Home page cards for live channels, plus a demo mode that shows Rocksmith-tagged Twitch streams with
Watch on TwitchandOpen playlistactions - Song search with direct viewer request actions, custom artist requests, copyable chat commands, catalog metadata, caching, and D1-backed rate limiting
- Public channel pages with playlist, played history, signed-in viewer request controls, live/request status badges, VIP token balance/help, and request timestamps
- Dashboard pages for account access, owner settings, admin controls, playlist management, and richer moderator tools
- Channel rules with setlists plus distinct artist, charter, song, and version blacklists
- Multi-version playlist metadata with version tables, download actions, and whole-song or version-specific moderation flows
- OBS-ready stream overlay settings with live preview, chroma-key background controls, and album/creator display toggles
- Twitch panel extension with playlist viewing, viewer request actions, and owner/moderator controls for play-now, reorder, remove, request-type changes, and other queue actions
- Shared bot-account OAuth, per-channel bot opt-in, and live-aware EventSub subscription management
- VIP token tracking with manual grants plus automatic rewards for new subs, shared resub messages, gifted subs, gift recipients, cheers, app-owned channel point rewards, raids, and StreamElements tips
- Internationalization support for localized website and Twitch panel UI, plus owner-controlled bot reply locales with English as the default
- Durable Object playlist serialization, Queue-based reply delivery, and Cloudflare-backed persistence
- Vitest, Playwright, and GitHub Actions verification
The app supports a shared Twitch bot account:
- broadcasters sign in with
/auth/twitch/start - an admin signs the shared bot in with
/auth/twitch/bot/start - streamers opt their own channel into bot presence from Dashboard Settings
- the app keeps
stream.onlineandstream.offlinesubscriptions for opted-in channels - when a channel is live, the app ensures
channel.chat.messageis subscribed for the broadcaster + bot-user pair - when VIP token automation is enabled, the app also manages the Twitch EventSub subscriptions needed for sub, gift, cheer, and raid reward flows
- when a channel goes offline, the chat subscription is removed
Detailed operator notes live in docs/bot-operations.md.
- Node 22+
- npm
- Wrangler installed through the repo dependencies
- Wrangler authenticated locally
Check Wrangler auth:
npx wrangler whoamiIf needed:
npx wrangler logincp .env.example .env.env.example:
APP_URL=http://localhost:9000
VITE_TWITCH_EXTENSION_API_BASE_URL=
SENTRY_ENVIRONMENT=development
SENTRY_DSN=
SENTRY_TRACES_SAMPLE_RATE=
TWITCH_CLIENT_ID=
TWITCH_EXTENSION_CLIENT_ID=
TWITCH_CLIENT_SECRET=
TWITCH_TOKEN_ENCRYPTION_SECRET=
TWITCH_EVENTSUB_SECRET=local-dev-eventsub-secret
TWITCH_EXTENSION_SECRET=
SESSION_SECRET=local-dev-session-secret
TWITCH_BOT_USERNAME=requestbot
TWITCH_SCOPES=openid user:read:moderated_channels moderator:read:chatters channel:bot channel:read:subscriptions bits:read channel:manage:redemptions
ADMIN_TWITCH_USER_IDS=
VITE_ALLOWED_HOSTS=For basic local development, set:
APP_URL=http://localhost:9000TWITCH_EVENTSUB_SECRETSESSION_SECRETTWITCH_BOT_USERNAMETWITCH_SCOPES=openid user:read:moderated_channels moderator:read:chatters channel:bot channel:read:subscriptions bits:read channel:manage:redemptionsVITE_ALLOWED_HOSTS=if you need extra Vite hostnamesVITE_TWITCH_EXTENSION_API_BASE_URL=if you want the standalone extension build to call a different app origin
To test Twitch sign-in, bot behavior, and EventSub locally, also set:
TWITCH_CLIENT_IDTWITCH_CLIENT_SECRETTWITCH_TOKEN_ENCRYPTION_SECRETADMIN_TWITCH_USER_IDS
To test the Twitch panel extension locally, also set:
TWITCH_EXTENSION_CLIENT_IDTWITCH_EXTENSION_SECRET
For a fuller panel setup and testing flow, see docs/twitch-panel-extension-local-test.md.
Use these Twitch values:
TWITCH_CLIENT_ID: Twitch application client ID for website sign-in and app API accessTWITCH_EXTENSION_CLIENT_ID: Twitch Extension client ID for the panel extensionTWITCH_EXTENSION_SECRET: base64 shared secret from the Twitch Extensions developer console
If you build the panel as a standalone Twitch extension artifact, set VITE_TWITCH_EXTENSION_API_BASE_URL to the public app origin that should receive /api/extension/* requests. For local iteration this can be your tunnel URL; for production it should be your deployed app URL.
ADMIN_TWITCH_USER_IDS should contain the Twitch user ID for the admin account that is allowed to connect the shared bot account and access admin pages.
Broadcaster connections need channel:bot, channel:read:subscriptions, bits:read, and channel:manage:redemptions in TWITCH_SCOPES. If the connected Twitch account is missing those permissions, reconnect Twitch from the app so bot replies, VIP token automation, and app-owned channel point rewards can use them.
App-owned channel point rewards also require a Twitch Affiliate or Partner channel. Twitch rejects custom reward create/update calls for channels that do not have channel points.
Sentry stays off locally unless you explicitly set a DSN:
SENTRY_DSN
If you want to test Sentry locally, use your own test DSN in .env and keep:
SENTRY_ENVIRONMENT=development
Production should keep using the deployed Worker secret for SENTRY_DSN.
URL split:
.envis for local development- if you use a Cloudflare Tunnel or ngrok URL locally, set that URL in
.envasAPP_URL .env.deployis for real Cloudflare deployments and should use the deployed public app URL, not the local tunnel URL
npm installnpm run db:bootstrap:localThat does three things:
- resets the local D1 database state
- applies local D1 migrations
- seeds the bundled sample catalog
The bundled sample catalog is intended for local development and preview deployments.
db:bootstrap:local is destructive for local D1 data by design.
For day-to-day schema changes during local development, use:
npm run db:migrateThat applies any new checked-in Drizzle SQL migrations to your local D1 database without resetting local data.
npm run devThe dev server runs on:
http://localhost:9000
The repo auto-runs local D1 migrations for dev, test, and build.
npm install sets up Husky automatically. The standard flow is:
git commit
git pushOn commit, the repo runs staged-file Biome fixes/checks. On push, it runs generated-file verification, typecheck, and tests.
Run the same push-time validation locally with:
npm run check:prepushnpm run lint uses a compact summary reporter. If you want Biome's full inline diagnostics, use npm run lint:full.
Run the full manual sequence only when you specifically want it:
npm run typecheck
npm run test
npm run format
npm run lint
npm run buildIf you changed browser-driven flows, also run:
npm run test:e2eIf you want Twitch login to work locally, your Twitch developer application must include both redirect URIs:
http://localhost:9000/auth/twitch/callbackhttp://localhost:9000/auth/twitch/bot/callback
Use the same Twitch application values in .env:
TWITCH_CLIENT_IDTWITCH_CLIENT_SECRET
localhost is enough for basic Twitch OAuth testing, but full local testing for this app is easier with a public HTTPS URL because EventSub/webhook flows need a reachable callback.
Primary option: Cloudflare Tunnel with a stable subdomain.
Install cloudflared, then authenticate:
cloudflared loginCreate a named tunnel:
cloudflared tunnel create request-bot-devCreate a DNS route for your dev hostname:
cloudflared tunnel route dns request-bot-dev dev.example.comIf that command tries to create the record in the wrong zone, or fails because you manage multiple domains in Cloudflare, create the DNS record manually in the correct zone instead:
- type:
CNAME - name:
dev - target:
<your-tunnel-id>.cfargotunnel.com - proxied:
On
Example:
dev.example.com -> 4ac1a27b-efe2-402a-a0ae-21ec35d61591.cfargotunnel.com
This manual DNS setup is the safer option when the hostname belongs to a different Cloudflare zone than the one cloudflared picks automatically.
Create ~/.cloudflared/config.yml on macOS/Linux, or %USERPROFILE%\.cloudflared\config.yml on Windows:
tunnel: <your-tunnel-id>
credentials-file: /home/<you>/.cloudflared/<your-tunnel-id>.json
ingress:
- hostname: dev.example.com
service: http://localhost:9000
- service: http_status:404Run the tunnel:
cloudflared tunnel run <your-tunnel-id>Then update:
.env:APP_URL=https://dev.example.comVITE_ALLOWED_HOSTS=dev.example.comif needed
- Twitch redirect URIs:
https://dev.example.com/auth/twitch/callbackhttps://dev.example.com/auth/twitch/bot/callback
Alternative: ngrok.
Install ngrok, then run:
ngrok http 9000Use the generated https://...ngrok-free.app URL as:
APP_URL- Twitch redirect URI base
For ngrok, remember:
- the URL may change between runs unless you use a reserved domain
- when the URL changes, update both
.envand the Twitch app redirect URIs
To test the shared bot flow locally:
- Sign in with your streamer account at
/auth/twitch/start. - Open
/dashboard/adminas an admin user. - Connect the shared bot account from the admin page.
- Complete the bot OAuth flow while logged into the Twitch account named in
TWITCH_BOT_USERNAME. - Open
/dashboard/settingsfor the streamer account and enable the bot for that channel.
Your Twitch bot account is separate from the streamer login. Regular users do not need bot-level scopes. The bot authorization flow is admin-only.
Without Twitch credentials in .env, contributors can run and review:
- homepage
- search
- public playlist pages
- most dashboard UI using the local data
Twitch sign-in, EventSub, and bot replies require valid Twitch app credentials.
For the expanded local setup notes, see docs/local-development.md.
npm run check:prepushFor extra confidence on deploy-sensitive changes, also run npm run build.
High-level deploy flow:
- Create the Cloudflare resources.
- Configure local deploy values in
.env.deploy, including the deployedAPP_URL. - Set Worker secrets with
wrangler secret put. - Bootstrap remote D1.
- Deploy the backend and frontend Workers.
- Optionally attach a custom domain to the frontend Worker.
- Register the Twitch redirect URIs for
APP_URL. - Configure GitHub Actions secrets and variables if you want automatic deploys.
The detailed deploy guide lives in docs/deployment-workflow.md.
Use these config templates for the first remote deploy:
Minimum resources:
- one D1 database
- one KV namespace
- one Queue
- the two Workers in this repo:
request-botrequest-bot-backend
Use Wrangler to create the resources:
npx wrangler login
npx wrangler d1 create request_bot
npx wrangler kv namespace create SESSION_KV
npx wrangler queues create twitch-reply-queueKeep the returned D1 and KV IDs for .env.deploy.
The committed Wrangler files stay as templates with placeholder IDs.
Set Worker secrets with Wrangler:
Frontend Worker:
echo "<TWITCH_CLIENT_ID>" | npx wrangler secret put TWITCH_CLIENT_ID --config wrangler.jsonc
echo "<TWITCH_CLIENT_SECRET>" | npx wrangler secret put TWITCH_CLIENT_SECRET --config wrangler.jsonc
echo "<TWITCH_TOKEN_ENCRYPTION_SECRET>" | npx wrangler secret put TWITCH_TOKEN_ENCRYPTION_SECRET --config wrangler.jsonc
echo "<TWITCH_EVENTSUB_SECRET>" | npx wrangler secret put TWITCH_EVENTSUB_SECRET --config wrangler.jsonc
echo "<TWITCH_EXTENSION_SECRET>" | npx wrangler secret put TWITCH_EXTENSION_SECRET --config wrangler.jsonc
echo "<SESSION_SECRET>" | npx wrangler secret put SESSION_SECRET --config wrangler.jsonc
echo "<ADMIN_TWITCH_USER_IDS>" | npx wrangler secret put ADMIN_TWITCH_USER_IDS --config wrangler.jsonc
echo "<SENTRY_DSN>" | npx wrangler secret put SENTRY_DSN --config wrangler.jsoncSet these non-secret panel values in .env.deploy:
TWITCH_EXTENSION_CLIENT_IDVITE_TWITCH_EXTENSION_API_BASE_URL
Backend Worker:
echo "<TWITCH_CLIENT_ID>" | npx wrangler secret put TWITCH_CLIENT_ID --config wrangler.aux.jsonc
echo "<TWITCH_CLIENT_SECRET>" | npx wrangler secret put TWITCH_CLIENT_SECRET --config wrangler.aux.jsonc
echo "<TWITCH_TOKEN_ENCRYPTION_SECRET>" | npx wrangler secret put TWITCH_TOKEN_ENCRYPTION_SECRET --config wrangler.aux.jsonc
echo "<TWITCH_EVENTSUB_SECRET>" | npx wrangler secret put TWITCH_EVENTSUB_SECRET --config wrangler.aux.jsonc
echo "<SENTRY_DSN>" | npx wrangler secret put SENTRY_DSN --config wrangler.aux.jsoncIf the Worker does not exist yet, wrangler secret put creates it and uploads the secret.
Copy the deploy env template:
cp .env.deploy.example .env.deployAdd the returned D1 and KV IDs to .env.deploy instead of editing the committed Wrangler files:
CLOUDFLARE_D1_DATABASE_ID=<d1 database id>
CLOUDFLARE_SESSION_KV_ID=<kv namespace id>
SENTRY_ENVIRONMENT=production
# Optional
SENTRY_TRACES_SAMPLE_RATE=0.1Use:
.envfor local development and your tunnel/ngrok URL.env.deployfor Cloudflare deployment values and the deployed app URL- the GitHub Actions
APP_URLrepository secret must also use the deployed public app URL, not the local tunnel URL
GitHub Actions does not use a checked-in .env.deploy file. The deploy workflows read:
- repository secrets for Cloudflare IDs and tokens
- the repository
APP_URLsecret for the deployed public URL - repository variables for non-secret runtime values such as
TWITCH_BOT_USERNAME,TWITCH_SCOPES,SENTRY_ENVIRONMENT, and optionallySENTRY_TRACES_SAMPLE_RATE
The repo generates gitignored deploy configs in .generated/ from .env.deploy, so your real Cloudflare IDs stay out of tracked files.
Sentry runtime notes:
- events are sent whenever
SENTRY_DSNis present - local development should use your own test Sentry DSN in
.env - the DSN should be set as a Cloudflare Worker secret on both deployed Workers
- release tagging uses the Worker version metadata binding automatically, with
SENTRY_RELEASEas an optional override - D1 access is instrumented automatically when Sentry is enabled
Seed the remote D1 database with the bundled catalog:
npm run db:bootstrap:remoteWith GitHub production deploys enabled, production schema changes go through pull requests and merge to main.
The production GitHub Actions workflow applies remote D1 migrations and deploys the Workers. Contributors run local migrations during development.
Remote-affecting npm scripts are guarded and exit with a helper message outside CI. That includes:
npm run db:migrate:remotenpm run db:seed:sample:remotenpm run db:bootstrap:remotenpm run deploy
If you intentionally need an operator-only override for maintenance, rerun them with ALLOW_REMOTE_OPERATIONS=1.
Deploy with the repo script:
npm run deploynpm run deploy builds the app first, generates gitignored deploy configs in .generated/, then deploys backend first and frontend second.
The app works with the frontend Worker's workers.dev URL or a custom domain.
If you want a custom domain after the first deploy:
- Open
Workers & Pagesin Cloudflare. - Select the frontend Worker:
request-bot. - Open
Settings->Domains & Routes. - Select
Add->Custom Domain. - Enter the hostname you use for the app.
Use the same URL in:
.env.deployAPP_URL- the GitHub Actions
APP_URLsecret - Twitch redirect URIs:
https://your-app-host/auth/twitch/callbackhttps://your-app-host/auth/twitch/bot/callback
If you build the standalone Twitch panel artifact, set VITE_TWITCH_EXTENSION_API_BASE_URL to the same app URL.
Verify the sample catalog with:
npx wrangler d1 execute request_bot --remote --config .generated/wrangler.production.jsonc --command "select count(*) as song_count from catalog_songs;"Once the repo is on GitHub, main can deploy automatically through the included GitHub Actions workflows.
Production deploys from GitHub Actions apply remote D1 migrations and then deploy the backend and frontend Workers. The remote migration script is blocked outside CI by default so normal contributor workflows stay local-only.
To enable GitHub deploys:
- Create the GitHub repository.
- Create the Cloudflare resources and store the D1/KV IDs in gitignored
.env.deployor deployment environment variables. - Add GitHub secrets:
CLOUDFLARE_API_TOKENCLOUDFLARE_ACCOUNT_IDCLOUDFLARE_D1_DATABASE_IDCLOUDFLARE_SESSION_KV_IDAPP_URL
- Use the deployed public app URL for the
APP_URLrepository secret. - Add GitHub variables:
TWITCH_BOT_USERNAMETWITCH_SCOPES
- Optionally add
CLOUDFLARE_WORKERS_SUBDOMAINfor preview deploy comments. - Protect
mainand require CI for merges.
Find the Cloudflare values here:
CLOUDFLARE_ACCOUNT_ID- Cloudflare dashboard
- select your account
- copy
Account IDfrom the account overview / Workers & Pages overview
CLOUDFLARE_API_TOKEN- Cloudflare dashboard
- profile icon →
My Profile API TokensCreate Token- create a token scoped to the account you are deploying into
Store those in GitHub Actions:
gh secret set CLOUDFLARE_API_TOKEN
gh secret set CLOUDFLARE_ACCOUNT_ID
gh secret set CLOUDFLARE_D1_DATABASE_ID
gh secret set CLOUDFLARE_SESSION_KV_ID
gh secret set APP_URL --body "https://your-app-host"
gh variable set TWITCH_BOT_USERNAME --body "your_bot_username"
gh variable set TWITCH_SCOPES --body "openid user:read:moderated_channels moderator:read:chatters channel:bot channel:read:subscriptions bits:read channel:manage:redemptions"For the full deploy and GitHub workflow details, use docs/deployment-workflow.md.
Set these as Codespaces repository secrets or add them to .env inside the Codespace:
APP_URLCLOUDFLARE_D1_DATABASE_IDCLOUDFLARE_SESSION_KV_IDTWITCH_CLIENT_IDTWITCH_EXTENSION_CLIENT_IDTWITCH_CLIENT_SECRETTWITCH_TOKEN_ENCRYPTION_SECRETTWITCH_EVENTSUB_SECRETTWITCH_EXTENSION_SECRETSESSION_SECRETVITE_TWITCH_EXTENSION_API_BASE_URLTWITCH_BOT_USERNAMEADMIN_TWITCH_USER_IDSTWITCH_SCOPES
For local development in this repo, the default app URL is http://localhost:9000.