Quiz Toolkit - Because Trivia Night Deserves Some Showmanship


Quiz Toolkit - Because Trivia Night Deserves Some Showmanship

Overview

Over the last four years, I’ve been deep into the quizzing rabbit hole—participating in and hosting dozens of trivia nights, pub quizzes, and family game nights. The moment I grew out of using PowerPoint slides, I hit a wall of technical limitations: how do i do buzzer rounds? how do I do audio/video clues? how do I collect answers from teams apart from them yelling it out loud.

For a while, I duct-taped together a workflow using open source buzzing web apps , WhatsApp groups, and a surprisingly clever Jeopardy board in Google Sheets (the Excel wizard that I am). It worked, sort of, but it lacked the showmanship I wanted—and it chained me to my laptop keyboard all night. Meanwhile, commercial trivia apps add needless friction with forced account signups, cloud storage, and recurring subscriptions. Much like GeoGuessr, I don’t want to pay a monthly subscription for something I pull out once in a while for friends and family.

I built Quiz Toolkit around a few core principles:

Host Page

How does Quiz Toolkit achieve that?

The first clear choice was to use a form of local browser storage for quizzes (IndexedDB) that persist across window reloads, yet your quiz data doesn’t leave your browser. For long-term backup and cross-device quiz sharing, I added an Export/Import feature. And because years of my own trivia history were locked in a battle-tested Google Sheet, I added one-click Google Sheets and CSV importers.

How to keep costs low? Well, not having to store data over the cloud helps. I also instinctively removed any user auth or account creation, both to remove user on boarding friction, and to cut costs. Hosts simply spin up a “room”; players scan a QR code (or enter a 6-character code) on their phones to join that room. No app store downloads, no passwords to reset.

Quizmaster Page

I also added a Quizmaster control. The host can project an answer-safe board onto a TV or projector while controlling the game from a separate private dashboard. Even better, scan a host QR code to use your smartphone as a handheld clicker while moving around the room.

Finally, there is a visual quiz designer which gives full customization control, and ability to set up different types of quiz formats, response methods and point systems.

Tech Stack & Architecture

Quiz Toolkit runs completely client-side in the browser:

Dual-Window Sync & Secret Isolation

Quiz Toolkit enforces strict private/public session isolation:

sequenceDiagram
    participant QM as QM Window (Authoritative Private State)
    participant Proj as Projection Layer (assertNoSecrets)
    participant BC as BroadcastChannel ('quiz-session-channel')
    participant Pres as Presentation Window (Filtered Public View)

    Note over QM: Holds complete state<br/>(unrevealed answers, QM notes)
    QM->>QM: QM executes action (e.g. selectClue)
    QM->>Proj: Generate PublicSessionState
    Proj->>Proj: Strip unrevealed answers & secrets
    Proj->>BC: Broadcast sync event
    BC->>Pres: Receive public state update
    Pres->>Pres: Render public view (question only)

    Note over QM: QM clicks Reveal Clue
    QM->>Proj: Generate PublicSessionState (now includes answer)
    Proj->>BC: Broadcast reveal event
    BC->>Pres: Render answer reveal on screen

Remote Transport: Durable Object Room Relay

Remote rooms use a Cloudflare Durable Object (RoomDurableObject) backed by SQLite, with WebSocket Hibernation API.

Key properties:

sequenceDiagram
    participant Host as Host Browser
    participant Worker as CF Worker (HTTP/WS routing)
    participant DO as RoomDurableObject (Cloudflare)
    participant Player as Player Browser

    Host->>Worker: POST /api/rooms {roomCode}
    Worker->>DO: Create/get stub for roomCode
    DO-->>Worker: Ready
    Worker-->>Host: {roomCode, status: ready}

    Host->>Worker: GET /ws/:roomCode?role=host
    Worker->>DO: upgradeWebSocket(role=host)
    DO-->>Host: 101 Switching Protocols (Hibernated WS)

    Player->>Worker: GET /ws/:roomCode?role=player
    Worker->>DO: upgradeWebSocket(role=player)
    DO-->>Player: 101 Switching Protocols (Hibernated WS)

    Host->>DO: STATE_UPDATE message
    DO->>Player: Relay STATE_UPDATE
    Player->>DO: pressBuzzer command
    DO->>Host: Relay pressBuzzer command

Learning And Next Steps

Setting up Cloudflare Durable Objects was easily the hardest part—it’s definitely not something you can build reliably with AI prompts alone. Also, building this over six weeks (mostly on nights and weekends) also meant embracing practical trade-offs. First, the buzzer is not perfect if 50 persons participate in a large pub and one person is on wifi while the other is using network data. Buzzer arbitrage is hard and can start brawls in a pub full of competitive drunk people over who buzzed first. I intend this to be used in groups of maximum 10-20 people. Second, the localstorage can make you lose your quiz data if you clear cache or if your browser is set-up to clear local storage on window close. If you forget to export and save your quiz, there is no cloud backup. But its a trade-off to keep the costs low.

What’s Next