Page Themes
Public shows use the default React SPA feed unless you pick a page theme under Page Customizations. Packaged themes are Liquid templates with CSS, optional images/fonts, and HarborFM mount points for interactive blocks (episodes, player, cast, reviews, and so on).
For show-level toggles (accent, which blocks appear), see Podcasts: Page Customizations. For listener-facing feed behavior, see Public feeds and embed.
Choosing a Theme
- Open the show and click Page Customizations.
- Pick a Page Theme:
- Default HarborFM - built-in React feed layout.
- Server-wide themes - Fluid, Folio, and any theme an admin has promoted.
- Imported themes - packages you imported under Themes (on podcasts you own).
- Optionally set Primary Color and visibility toggles (author, descriptions, funding, podroll, cast, reviews, and related blocks).
The picker shows package preview images when the theme provides one. When a theme sets homepage in theme.json, Preview opens that live URL.
Visibility toggles and accent color apply to both Default and packaged themes. Packaged themes can read accent colors and show.* flags in template context.
Themes Studio
Users with Can Import Theme can open Themes to:
- Browse Server Themes (preview thumbnails, description, live Preview when
homepageis set) - Download a server theme as a zip
- Import a theme zip (max 10 MB; rate limited to 2 per minute per user)
- Edit an imported copy in the near-fullscreen theme editor (name, version, home template, page routes, Liquid/CSS/images/fonts)
Re-importing the same theme.json id updates that theme in place (including personal copies of server-wide themes). Theme files count toward the account storage limit.
Assigning a server-wide theme id uses the data-backed server package. To use your fork, pick your imported copy’s row in Page Customizations.
Admins
Admins can promote a personal theme to a server-wide theme (available to every show), demote it back, delete server themes, and edit server theme files. Admin edits and promote set allowOverride: false so customized server themes are not overwritten on upgrade.
Built-Ins and Storage
Fluid and Folio ship with HarborFM and are seeded into {DATA_DIR}/themes/server on first boot. Folio is multi-page (home plus about, crew, support, connect, episodes). Both include preview images, homepage demo links, and optional not_found templates for unknown theme .html routes.
On upgrade, HarborFM replaces a data copy only when its theme.json still allows override (default) and the shipped version differs. Packages under {DATA_DIR}/themes/server persist across Docker image updates.
Gallery and Authoring Docs
Community/gallery themes (with previews) are published from harborfm-themes and listed on the docs Themes page.
Authoring guides:
- Docs: Theme Authoring Guide
- Repo / AI skill: theme-SKILL.md (also downloadable as SKILL.md from the Themes page)
Local developers can clone harborfm-themes/ next to the HarborFM tree and run pnpm themes:sync to copy gallery packages into {DATA_DIR}/themes/server. See the main README.
Package Layout
theme.json required (id, name, version; optional fields below)
templates/podcast.liquid required
templates/episode.liquid required
templates/*.liquid optional extra pages and _partials
css/ optional (*.css; all auto-linked)
images/ optional (png, jpg, jpeg, gif, webp)
fonts/ optional (.woff2, .ttf)
Optional theme.json fields:
| Field | Purpose |
|---|---|
description |
Short blurb for Themes cards and the gallery |
preview |
Path under images/ for picker / gallery cards |
homepage |
https URL for live Preview |
index |
Home template basename (default podcast) |
pages |
Map template basename → public .html filename |
not_found |
Template for unknown theme pages (HTTP 404; not a public page) |
allowOverride |
When false, shipped upgrades do not overwrite this server theme |
Extra templates become /feed/{slug}/{page}.html (or /{page}.html on a custom domain). Theme pages are included in the podcast sitemap.
Mounts and Actions
Interactive HarborFM blocks use Liquid mounts:
{% render 'harborfm/episodes' %}
{% render 'harborfm/player' %}
{% render 'harborfm/search' %}
{% render 'harborfm/reviews' %}
{% render 'harborfm/cast' %}
{% render 'harborfm/funding' %}
{% render 'harborfm/links' %}
{% render 'harborfm/podroll' %}
{% render 'harborfm/site_header' %}
{% render 'harborfm/show_header' %}
{% render 'harborfm/breadcrumbs' %}
Put harborfm/episodes on exactly one page. Themes can also wire native controls:
<button type="button" data-harborfm-action="subscribe">Subscribe</button>
<button type="button" data-harborfm-action="share">Share</button>
<a href="{{ podcast.rss_url }}" data-harborfm-action="feed">RSS</a>
<button type="button" data-harborfm-action="message">Message</button>
<button type="button" data-harborfm-action="alerts">Alerts</button>
<button type="button" data-harborfm-action="write-review">Write a review</button>
Unavailable actions are hidden automatically.
Script tags, event handlers, javascript: URLs, and the Liquid | raw filter are rejected on import.
Liquid Context and Styling
Useful context includes podcast, episode / episodes, cast, funding_links, links, podroll, reviews, theme (id/name/version/homepage), accent (including accent.fg for text on accent), show (visibility booleans), urls (home, pages, theme_asset_base), site, and page.
Accent CSS variables include --accent, --accent-dim, --accent-glow, and --accent-fg.
Dialogs (Message, Get Alerts, Share, Subscribe, Write Review) render under themed chrome so theme CSS can style them, for example:
[data-harborfm-dialog="feedback"] { /* Message */ }
[data-harborfm-dialog="alerts"] { /* Get Alerts */ }
[data-harborfm-dialog="share"] { /* Share */ }
[data-harborfm-dialog-overlay="feedback"],
[data-harborfm-dialog-overlay="alerts"] { /* backdrop */ }
Theme partials (templates/_nav.liquid, etc.) use {% render %} with an isolated scope. Pass context explicitly:
{% render '_nav', urls: urls, podcast: podcast, site: site, page: page %}
Who Can Import Themes
defaultCanImportTheme(Settings > Default Limits) - whether newly registered users can import and edit themes. Default is on.canImportTheme(Users admin) - per-user flag. When off, that user cannot import, download, or edit theme files (403). Selecting a built-in or server theme on a show they manage still works when the theme is available.
See Admin and settings.