HowlCastHowlCast
Reference

Schema

Every table, column, and index in HowlCast's D1 database.

Authoritative source: packages/db/src/schema/. Migrations in packages/db/src/migrations/.

Auth (Better Auth)

Standard Better Auth schema. Tables: user, account, session, verification, plus plugin tables for passkey and two_factor.

Won't reproduce the columns here — see Better Auth docs.

profiles

One row per user. Holds all the human-facing identity bits.

ColumnTypePurpose
user_id PKtextFK → user.id cascade delete
display_nametextShown above the player + chat user card
biotext?Long-form, free text
pronounstext?Optional
avatar_keytext?R2 key in howlcast-public
banner_keytext?R2 key (currently unused in UI)
verifiedbooleanCyan check mark next to display name
roleenum'broadcaster' | 'viewer'
is_invitedbooleanSingle permission flag — chat-write access
invited_atts
invited_bytext?FK → user.id (no cascade — historical only)

Indexes: role, is_invited.

channel_config

Single row, id = 'site'. Site-level state.

ColumnPurpose
id PKAlways 'site'
owner_idFK → user.id (the broadcaster)
titleStream title shown over player
live_started_atSet by call.live_started webhook
live_ended_atSet by call.session_ended webhook
stream_call_idGetStream call id
chat_channel_cidGetStream chat channel id
rtmps_urlRTMPS ingress URL persisted from GetStream
broadcaster_twitch_idNumeric Twitch user ID
setup_completed_atLocks the /setup wizard once set

panels

Cards that show below the player.

ColumnPurpose
id PKUUID
positionSort order, integer
titleCard heading
bodyCard body, plain text
image_keyR2 key (currently unused in UI)
link_urlIf set, the card becomes a link

Indexes: position.

invites

Magic-link invite codes for new viewers.

ColumnPurpose
code PK32-char random string
emailRecipient email
created_byFK → user.id (the broadcaster)
created_atts
expires_at30 days from creation
used_atSet when the invitee accepts
used_byFK → user.id
revoked_atSet when the broadcaster revokes

Indexes: created_at.

discord_webhooks

Two rows, ids 'public' + 'private'.

ColumnPurpose
id PK'public' | 'private'
urlDiscord webhook URL (nullable)
notify_on_liveboolean
notify_on_endboolean
last_fired_atts of last successful POST
last_errorIf non-null, last delivery error

stream_sessions

One row per live session. Written by the GetStream webhook handler.

ColumnPurpose
id PKUUID
call_idGetStream call id
started_atFrom call.live_started
ended_atFrom call.session_ended
peak_viewersHigh watermark from join events (broadcaster excl.)
chat_message_countBumped from message.new events
total_minutesComputed at session end

Indexes: started_at.

stream_viewer_snapshots

Time-series viewer counts. Powers the line chart.

ColumnPurpose
id PKUUID
session_idFK → stream_sessions.id cascade delete
sampled_atts
viewer_countInteger

Indexes: (session_id, sampled_at). Pruned to 7 days by hourly cron.

stream_chat_minutes

Per-minute chat counts. Powers the bar chart.

ColumnPurpose
session_idFK → stream_sessions.id cascade delete
minute_bucket_msMillis floored to the minute
countBumped via UPSERT

Composite PK on (session_id, minute_bucket_ms). UPSERT key matches.

white_label

Single row, id = 'site'.

ColumnPurpose
id PKAlways 'site'
custom_logo_keyR2 key (nullable)
custom_platform_nameOverride "HowlCast" (nullable)
footer_attributionenum: 'default' | 'custom' | 'off'
custom_footer_textUsed when footer_attribution = custom
updated_atts

Two rows, ids 'privacy' + 'terms'.

ColumnPurpose
id PK'privacy' | 'terms'
body_htmlSanitized HTML from Tiptap editor
updated_atts

Migrations

Numbered 0000_… through 0009_… at time of writing. Each is a SQL file plus a journal entry in _journal.json. Apply via bun run db:migrate:remote.

#TagSummary
0000yielding_major_mapleleafBetter Auth tables
0001bumpy_bushwackerprofiles, channel_config, panels
0002supreme_master_chiefinvites + discord_webhooks
0003thin_mole_manpasskey + 2fa plugin tables
0004lean_nocturnedrop allow_signups
0005stream_sessions_and_rtmps_urlsessions table + RTMPS column
0006white_label_and_legal_docswhite-label + legal-docs
0007drop_user_bans_and_invite_indexesdropped unused user_bans table
0008drop_visibilitydropped public mode
0009stream_analyticsviewer snapshots + chat minutes + counts

Next: Env vars.

On this page