HowlCastHowlCast
Configure

Branding

White-label settings and the Tiptap legal editor.

HowlCast ships with a default brand (HowlCast / navy + cyan / wolf-themed-but-understated). The whiteLabel table lets a fork swap logo, platform name, and footer attribution without touching code.

Dashboard → Branding has one form with three fields:

Logo upload

PNG, SVG, or JPEG, max 1 MB. Multipart POST to /api/upload/logo (5/hour throttle per user). Bytes go directly to R2 (branding/logo-<hash>.<ext>); the hash key prevents collisions and is content-addressed so re-uploading the same file is a no-op.

Reads route through /api/branding/logo to pipe through Next.js <Image> for optimization. The R2 bucket URL is intentionally not exposed.

Reset to default reverts the customLogoKey column to NULL — the BrandMark SVG component in @howlcast/ui renders instead.

Platform name

Defaults to "HowlCast". Set a custom string (24 char max) and it shows in:

  • Site nav
  • Email subject lines + body
  • OG images (apps/web/src/app/opengraph-image.tsx)
  • Site footer

Three choices:

  • Default: Powered by <platformName> by MrDemonWolf, Inc.
  • Custom: any string up to 80 chars
  • Off: nothing

useWhiteLabel (apps/web/src/lib/use-white-label.ts) is the reactive hook that reads the table; the dashboard form mutates via branding.update.

Privacy + Terms

Dashboard → Branding → Privacy & Terms tabs. Tiptap WYSIWYG editor with bold, italic, headings, bullets, ordered lists, links, undo / redo. Saves on click.

Server sanitizes the HTML on save via rehype-sanitize (packages/api/src/lib/legal-sanitize.ts) — script tags, inline event handlers, and weird protocols are stripped before the row hits D1. Public reads at /privacy and /terms use dangerouslySetInnerHTML directly because sanitization happened at write time.

The default templates ship with a placeholder. Replace them on first deploy or you'll publish a placeholder.

Reading the data

Public read: branding.get returns the resolved white-label values (custom-or-default). Always returns a value. Used by the public channel page.

Broadcaster read: branding.get returns the same shape. Mutate via branding.update.

Tables involved: white_label, legal_docs. See Schema.

Removing branding entirely

To strip MrDemonWolf attribution: Branding → Footer attribution → Off. The white-label form persists footerAttribution = "off" and the footer renders nothing.

This doesn't remove the legal disclaimers in /privacy and /terms — those are governed by your edited templates.

Next: Notifications.

On this page