A Discourse theme component that sends pageviews and rich custom events to Plausible Analytics — Cloud or self-hosted. Beyond basic page views it tracks topic creation, replies, likes, bookmarks, searches, outbound link clicks, and (optionally) any Discourse appEvent you name.
It exists because just pasting Plausible's snippet into a theme's </head>
can't do any of the Discourse-specific work: hooking forum activity as custom
events, keeping private messages out of your analytics, and redacting
one-time-token URLs before they leave the browser.
Requires Discourse 3.3 or later: the component injects the Plausible
script from theme JavaScript, which Discourse's strict-dynamic CSP trusts
automatically — no CSP settings to change. (On older Discourse versions you'd
have to add your Plausible host to the content_security_policy_script_src
site setting.)
Event hooks (topic:created, post:created, page:like-toggled,
bookmarks:changed) verified against Discourse core main as of July 2026.
All handlers are defensively guarded, so a missing or reshaped event degrades
to "that event isn't tracked" rather than a broken site.
- The official Plausible script, in manual mode. The component loads
script.manual.jsfrom your Plausible instance, so everything the official script does — visit attribution, referrers, engagement time and scroll depth, theplausible_ignoreopt-out — works exactly as on any other Plausible site. "Manual" means the script sends nothing on its own: every event is triggered by the component, which applies the privacy rules below first. - Pageviews are sent on
page:changed, which fires on the initial load and on every in-app route transition (deduplicated by URL). - Custom events are subscribed via Discourse
appEvents(topic:created,post:created,page:like-toggled,bookmarks:changed), searches are detected from the/search?q=…route, and outbound clicks from a capture-phase click listener (the component's own, not the script's outbound-links extension, so the private-message rule applies to those too). - Privacy defaults: no cookies, no user identifiers, private messages and
whispers are never tracked, admin pages send nothing by default, search
terms are excluded unless you opt in, and the
plausible_ignorelocalStorage flag is honored.
| Plausible event | Fires when | Props | Toggled by |
|---|---|---|---|
pageview |
Initial load and every in-app navigation | — | track_pageviews |
Topic Created |
A user creates a new topic (PMs excluded) | category |
track_topic_created |
Post Created |
A user posts a reply (PMs and whispers excluded) | category |
track_post_created |
Post Liked |
A user likes a post (unliking is not tracked) | category |
track_likes |
Post Bookmarked |
A user bookmarks a post | category |
track_post_bookmarks |
Topic Bookmarked |
A user bookmarks a whole topic | category |
track_topic_bookmarks |
Search |
A full-page search (/search?q=…) is executed |
term (opt-in) |
track_search |
Outbound Link: Click |
A visitor clicks a link to another site | url |
track_outbound_links |
| (your own) | Any appEvent listed in forward_app_events |
— | forward_app_events |
Every event has its own on/off setting in the component's admin UI. On top of
these, the official script sends its own engagement events automatically,
powering the time-on-page and scroll-depth metrics in the dashboard.
- Add your forum as a site in Plausible and note the domain you used.
- Custom events only show up once you add them as goals: Site settings → Goals → Add goal → Custom event, using the exact names from the table above.
- To break goals down by
category,term, orurl, enable those keys under Site settings → Custom properties.
Login and sign-up funnels need no custom events — modern Discourse serves them
as full pages, so /login and /signup can be tracked as pageview goals.
- Admin → Customize → Themes → Components → Install → From a git repository and paste this repo's URL (or From your device with a zip).
- Add the component to your active theme.
- In the component settings, set
plausible_domainto the domain configured in Plausible (or leave blank to use the forum's hostname). Self-hosters: pointplausible_hostat your instance. - Hard-refresh the forum and watch the events arrive in your Plausible
dashboard. Enable
debug_modeto see every payload in the browser console.
| Setting | Default | What it does |
|---|---|---|
plausible_host |
https://plausible.io |
Your Plausible instance — the script is loaded from (and events sent to) here |
plausible_domain |
(forum hostname) | Which Plausible site records the events (data-domain) |
track_pageviews |
on | SPA pageviews |
track_topic_created |
on | Topic Created events |
track_post_created |
on | Post Created events |
track_likes |
on | Post Liked events |
track_post_bookmarks |
on | Post Bookmarked events |
track_topic_bookmarks |
on | Topic Bookmarked events |
track_search |
on | Search events |
include_search_term |
off | Attach the query as a term prop (may contain PII) |
track_outbound_links |
on | Outbound Link: Click events |
include_category_prop |
on | Attach the category name to content events |
track_logged_in_users |
on | Turn off to track only anonymous visitors |
exclude_staff |
off | Never track admins/moderators |
exclude_admins |
off | Never track admins (moderators still tracked) |
exclude_admin_pages |
on | No events at all on admin pages (/admin…) |
exclude_personal_pages |
off | No events at all on /u/… and /my/… pages |
forward_app_events |
(empty) | Extra appEvents to forward verbatim |
track_localhost |
off | Also send events from localhost |
debug_mode |
off | Log every payload to the browser console |
The component description and every setting description shown in the admin UI
are localized via locales/*.yml (Discourse reads them from
theme_metadata, matching the admin user's locale and falling back to
English). English, German, Spanish, French, and Dutch are included. Add a
language by dropping a locales/<code>.yml alongside en.yml with the same
keys. Note that setting names (e.g. track_pageviews) and the Plausible
event names themselves are not translatable — only the descriptions are.
Two escape hatches, no code changes required for the first:
forward_app_events— list any Discourse appEvent names (e.g.composer:opened) and each is forwarded as a Plausible event of the same name. Discover event names by grepping Discourse core forappEvents.trigger.window.plausible("Event Name", { props: { … } })— the component exposes the same global the official script does, so other theme components and plugins can send their own events through it.
- Nothing is stored on the visitor's device; no cookies or fingerprinting.
- Private messages and whispers are never tracked, regardless of settings.
This is enforced centrally: on a private-message page (the PM itself or the
messages inbox) no event of any kind is sent — not pageviews, not
outbound clicks, not forwarded appEvents, not
window.plausible()calls — because a PM's URL contains its title-derived slug. One consequence of the official script measuring engagement against the last sent pageview: time spent on an untracked page (a PM, or an excluded admin/personal page) can be attributed to the previously viewed tracked page. The untracked URL itself is never sent. - The same no-events-at-all rule covers admin pages (
/admin…) by default (exclude_admin_pages) — their URLs and search filters can reveal internal details — and, opt-in viaexclude_personal_pages, the user profile and preferences pages (/u/…,/my/…), whose URLs contain usernames. - URLs of one-time-token pages (password reset, email login, account
activation, email confirmation, invite redemption, account association) are
redacted to
…/redactedbefore sending, so tokens never leave the browser. include_category_propwill include the names of restricted categories when their members trigger events — turn it off if your category names are themselves sensitive.- Visitors can opt out by setting
localStorage.plausible_ignore = "true", same as with the official script.
about.json
settings.yml
locales/en.yml
javascripts/discourse/api-initializers/plausible-analytics.js