A fully customisable, browser-source-ready spin wheel for live streamers on Twitch, Kick, and more. Runs entirely on your own machine — no cloud, no subscription, no latency.
- Add, remove, drag-and-drop reorder, and weight segments
- Per-segment colour picker (10-colour palette + full hex input)
- Per-segment advanced options: text colour, gradient fill, font override, label position offset
- Smart label sizing — all labels sit at the same radius; font scales automatically so wide segments get bigger text and narrow segments get smaller text, everything stays inside the wheel
- Global font selector — 37 fonts with live preview (each option rendered in its own typeface), grouped by style: sans-serif, condensed, display, handwriting, serif, gaming, monospace, and custom
- Bold / italic label style toggles
- Border, hub, glow, drop shadow, and background colour controls
- Frame ring width control (reserves space for artist frame overlays)
- Frame overlay upload — drop a transparent PNG designed by your artist on top of the wheel
- Upload one image that fills the entire wheel circle — segments act as windows into it
- Five modes:
- None — no image, normal solid segments
- All — every segment shows the image
- Alternating — even segments show image, odd segments show solid colour
- Manual — per-segment toggle controls which segments show the image
- Reveal — segments start solid; each winning segment permanently reveals its slice of the image, building up the full picture over multiple spins
- Image opacity and text readability overlay (dark veil) sliders
- Reset Reveals button to wipe all segments back to solid and start a new game
- Five built-in presets: Arrow, Triangle, Pin, Gem, Hand (live canvas previews)
- Import your own PNG/JPG pointer image (auto-resized to 256px)
- Custom pointer rotation: snap buttons (−90°, −45°, +45°, +90°) + full 360° slider
- Position: top, right, bottom, left
- Scale and colour tint controls
- Save unlimited named wheel configurations
- Switch between wheels instantly — OBS overlay updates live
- Active preset remembered across page reloads (no accidental duplicates)
- Timestamps show when each preset was last saved
- Every spin result is recorded — label, colour swatch, timestamp, and who triggered it (Twitch username, webhook, etc.)
- Persists across restarts (
history.json, capped at 200 entries) - Live updates in the editor as spins complete — no refresh needed
- Hover any row to reveal edit (✎) and remove (✕) actions
- Edit: correct the prize label or viewer name inline, Enter to save
- Remove: mark the prize as claimed and remove it from the list
- Clear all button to reset history
- Configurable win message with
{winner}placeholder - Background colour, opacity, font, size, and text colour
- Adjustable display duration
- Post-result linger — hold the wheel on screen for an extra 0–10s after the overlay fades, useful with Reveal mode so viewers can see which slice was uncovered before the next spin fires
- Pop-in animation shown in both the editor preview and the OBS overlay
| Platform | Chat Commands | Channel Points | Trigger Method |
|---|---|---|---|
| Twitch | ✅ | ✅ | Chat, Channel Points, Webhook |
| Kick | Via Botrix | — | Botrix → Webhook |
| Any tool | — | — | POST /api/trigger |
- Paste
http://localhost:3000/wheelas a Browser Source (any size, transparent background) - Config changes push live to the overlay via WebSocket — no refresh needed
- Spin queue handles multiple simultaneous triggers
Any tool that can make an HTTP POST can fire the wheel:
curl -X POST http://localhost:3000/api/trigger \
-H "Content-Type: application/json" \
-d '{"secret": "YOUR_WEBHOOK_SECRET"}'Stream Deck users: install the Web Requests plugin and point a button at this endpoint.
- Node.js v20+
- OBS Studio or Streamlabs (any version with Browser Source)
git clone https://github.com/Djpopple/streamspin.git
cd streamspin
npm installcp .env.example .envOpen .env in any text editor and set WEBHOOK_SECRET to any random string (e.g. mysecretword123). You can fill in Twitch credentials later.
npm run devOpen http://localhost:5173 in your browser — that's the editor. The OBS overlay URL is always http://localhost:3000/wheel.
Press Ctrl+C in the terminal then run npm run dev again.
This is a one-time setup. Once done the wheel appears on stream whenever your scene is active.
- In OBS, open the scene you stream from
- In the Sources panel, click the + button
- Choose Browser Source
- Name it something like
StreamSpin Wheeland click OK
Fill in the settings exactly as follows:
| Setting | Value |
|---|---|
| URL | http://localhost:3000/wheel |
| Width | 1920 (or your stream canvas width) |
| Height | 1080 (or your stream canvas height) |
| Custom CSS | leave completely blank |
| Shutdown source when not visible | OFF — this must be unchecked |
| Refresh browser when scene becomes active | optional, either is fine |
Why "Shutdown when not visible" must be OFF: If this is on, OBS disconnects the wheel from StreamSpin every time you switch scenes. When you switch back, the wheel won't respond to spins until it reconnects, which can take several seconds.
Click OK.
The wheel renders with a transparent background so it layers cleanly over your stream layout. Resize and position the browser source however you like — drag it, use the alignment tools, or enter exact coordinates in the Transform panel (Ctrl+E).
Make sure StreamSpin is running (npm run dev), then press Space in the editor. The wheel should spin in OBS at the same time.
If the wheel doesn't appear in OBS, right-click the browser source and choose Refresh to force a reconnect.
StreamSpin connects to Twitch in two ways:
- Chat commands — viewers type
!spinin your chat to trigger a spin - Channel Points — a spin triggers automatically when a viewer redeems a custom reward
You need to set up a Twitch application to enable either of these.
This is a one-time setup that tells Twitch that StreamSpin is allowed to connect to your channel.
- Go to dev.twitch.tv/console and log in with your Twitch account
- Click Register Your Application
- Fill in the form:
- Name: StreamSpin (or anything you like)
- OAuth Redirect URLs:
http://localhost:3000/auth/twitch/callback - Category: Broadcaster Suite
- Click Create
- On the next page, click Manage next to your new application
- Copy the Client ID — you'll need this in a moment
- Click New Secret, copy the Client Secret immediately (it's only shown once)
Open your .env file and fill in:
TWITCH_CLIENT_ID=paste_your_client_id_here
TWITCH_CLIENT_SECRET=paste_your_client_secret_here
TWITCH_REDIRECT_URI=http://localhost:3000/auth/twitch/callback
Save the file and restart StreamSpin (Ctrl+C then npm run dev).
- Open the editor at
http://localhost:5173 - Open the Integrations panel in the sidebar
- Enter your Twitch channel name (the name in your stream URL, e.g.
mychannel) - Click Connect to Twitch
- Your browser will open a Twitch authorisation page — click Authorise
- The browser tab closes automatically and the status in the editor turns green
StreamSpin stores your login tokens locally and refreshes them automatically — you won't need to do this again unless you revoke access.
In the Integrations panel, turn on Chat Commands. Viewers can now type in your chat:
| Command | Who can use it | What it does |
|---|---|---|
!spin |
Everyone (if enabled) | Triggers a spin |
!addslice <label> |
Moderators and broadcaster | Adds a new segment to the wheel live |
!removeslice <label> |
Moderators and broadcaster | Removes a segment from the wheel live |
You can restrict !spin to subscribers only from the Integrations panel.
Channel Points lets viewers spend their points to trigger a spin — no chat command needed.
- Go to your Twitch dashboard → Viewer Rewards → Channel Points → Manage Rewards
- Click Add Custom Reward
- Name it (e.g. "Spin the Wheel"), set a point cost, and save
- Back in the Twitch dashboard, open your browser's developer tools (F12), go to the Network tab, and reload the page — find the
custom_rewardsAPI call and copy the reward's ID from the response. Alternatively, ask a moderator to help find it via the Twitch API explorer at dev.twitch.tv/docs/api/reference - Paste the reward ID into the Channel Points Reward ID field in StreamSpin's Integrations panel
- Enable Channel Points in the panel
When a viewer redeems the reward, a spin fires automatically and the redemption is marked as fulfilled.
Kick doesn't have an official bot API, so StreamSpin uses Botrix as a bridge — Botrix handles the Kick side, StreamSpin handles the wheel side.
- Download and install Botrix
- Connect Botrix to your Kick channel (follow Botrix's own setup guide)
- In Botrix, go to Commands and create a new command called
!spin - Set the action type to Custom JavaScript
- Paste in this code, replacing
YOUR_WEBHOOK_SECRETwith the value from your.envfile:
fetch('http://localhost:3000/api/trigger', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ secret: 'YOUR_WEBHOOK_SECRET', triggeredBy: 'kick:' + username })
});- Save the command
When a viewer types !spin in your Kick chat, Botrix calls StreamSpin's trigger endpoint and the wheel spins. Botrix must be running alongside StreamSpin for this to work.
- Open the Segments panel and click Add Segment to add prizes
- Click a segment to expand it — you can change the label, colour, weight (how likely it is to land), and advanced options like gradient, font, and text colour
- Drag the ⠿ handle on the left of any segment to reorder them
- Open Appearance to change the overall wheel style — background, fonts, shadows, and frame overlay
- Open Pointer to choose or upload a custom pointer
- Open Spin Settings to adjust how long the wheel spins
- Open Result Overlay to configure the winner announcement that appears after each spin
The preset bar runs along the top of the editor. Type a name and click Save to create a new preset. Click Update to save changes to the current preset. Click any preset name to switch to it — the OBS overlay updates immediately.
Press Space anywhere in the editor (as long as you're not typing in a text field) to trigger a test spin. The wheel spins in both the editor preview and the OBS overlay.
Open the Appearance panel and scroll to the Segment Image section.
- Choose a mode — All, Alternating, Manual, or Reveal
- Upload your image (any format — PNG, JPG, etc.)
- Adjust Image opacity and Text readability overlay to taste
Reveal mode step by step:
- Set mode to Reveal and upload a hidden image
- All segments start solid — the image is completely hidden
- Each time a segment wins, it permanently flips to transparent, revealing that slice of the image
- Progress is saved automatically — if you restart the stream, the reveal state is preserved
- When you're ready to start fresh, click Reset all reveals to return all segments to solid
Tip: Pair Reveal mode with the Hold wheel after result slider in the Result Overlay panel — set it to 2–4 seconds so viewers have time to appreciate each reveal before the next spin starts.
Open the Win History panel to see a log of every spin result. Hover any row to see two buttons:
- ✎ — click to edit the label or viewer name (useful if you want to note who received a prize)
- ✕ — click to remove the entry (use this to mark prizes as claimed)
History survives restarts. Click Clear all to start fresh.
Click Import in the editor header and select any .json file from the presets/ folder in the StreamSpin directory. This loads a ready-made theme you can customise from.
| File | Vibe |
|---|---|
halloween.json |
Spooky orange and purple |
goth.json |
Dark, moody, slow spin |
cutesy.json |
Soft pastels, bouncy |
cottage-core.json |
Earthy and gentle |
christmas.json |
Festive red, green, and gold |
| Key | Action |
|---|---|
Space |
Test spin (when not focused on a text input) |
For artists creating frame overlays or custom pointers:
Frame overlay PNG:
- Square canvas at your target resolution (e.g. 800×800px)
- Transparent centre — the wheel renders behind the frame
- Design vines, cogs, borders etc. in the outer ring
- Use "Frame ring width" in Appearance to control ring size (default 56px, up to 160px)
- Export as PNG with alpha
Custom pointer PNG:
- 64×64px recommended (auto-resized on import)
- Tip pointing right (3 o'clock) — StreamSpin rotates it to the correct position
- Use the rotation controls in the Pointer panel to fine-tune
Drop font files (.ttf / .woff2) into public/assets/fonts/ and ask Claude to wire up the @font-face rule.
streamspin/
├── index.html ← Vite entry: editor app
├── overlay.html ← Vite entry: OBS overlay
├── presets/ ← Ready-to-import themed wheel configs
├── public/
│ └── assets/fonts/ ← Drop custom font files here
├── src/
│ ├── app/ React editor UI
│ │ ├── components/
│ │ │ ├── panels/ Segments, Appearance, Pointer, Spin, Result, Integrations, History
│ │ │ ├── ui/ Slider, Toggle, ColorInput, Select, Panel, …
│ │ │ ├── PresetManager.tsx
│ │ │ └── WheelPreview.tsx
│ │ └── lib/ configApi, constants
│ ├── wheel/ Pure Canvas renderer — no React dependency
│ │ ├── renderer.ts renderFrame, smart label sizing, frame/pointer caches
│ │ ├── physics.ts Spin animation, winner detection, easing
│ │ └── pointers.ts Five canvas-drawn pointer presets
│ ├── overlay/ OBS overlay entry (vanilla TS)
│ ├── server/ Express + Socket.io backend
│ │ ├── routes/ config, trigger, presets, auth, history
│ │ ├── configStore.ts Atomic config.json read/write
│ │ ├── migration.ts Schema migration — runs on every config read
│ │ ├── presetsStore.ts Atomic presets.json read/write
│ │ ├── historyStore.ts Win history — in-memory + history.json persistence
│ │ ├── socketBridge.ts Spin queue + event routing + win recording
│ │ ├── tokenStore.ts Twitch OAuth token storage + auto-refresh
│ │ └── integrationManager.ts Twitch chat + EventSub lifecycle
│ ├── integrations/
│ │ └── twitch/ chat.ts (tmi.js) + eventsub.ts (native WS)
│ └── types/
│ ├── config.ts Master WheelConfig schema + DEFAULT_CONFIG
│ └── events.ts Socket.io event maps
├── docs/
│ ├── ARCHITECTURE.md
│ ├── INTEGRATIONS.md
│ └── FILE-GUIDE.md Plain-language guide to every file and folder
├── .env.example
├── CLAUDE.md
└── TODO.md
The wheel doesn't appear in OBS
Right-click the browser source and click Refresh. Make sure StreamSpin is running (npm run dev).
The wheel appears but doesn't spin when chat commands are used
Check the Integrations panel — the Twitch status indicator should be green. If it's red or grey, click Connect again. Make sure your .env has the correct Client ID and Secret.
OBS shows the wheel but it doesn't update when I change settings Make sure "Shutdown source when not visible" is OFF in your browser source settings. If it is off and it's still not updating, right-click the source and choose Refresh.
The result overlay doesn't appear Open the Result Overlay panel in the editor and make sure it's toggled on.
Spins are queuing up and not firing This can happen if the overlay disconnected mid-spin. Right-click the OBS browser source and refresh it to reset the connection.
Phases 0–4 complete including segment images, reveal mode, and win history. Next: Phase 5 — Electron packaging into a single .exe.
See TODO.md for the full breakdown.
PRs welcome. Please read CLAUDE.md for development conventions if using AI-assisted coding.
MIT