Hugo plugin for pulling comments from ActivityPub
  • Go 46.9%
  • JavaScript 26.9%
  • HTML 15.3%
  • CSS 9.2%
  • Dockerfile 1.7%
Find a file
James Wynn 304448fbf5
All checks were successful
CI / build (push) Successful in 13s
Release / release (push) Successful in 8s
Merge pull request 'feat: generic "Fediverse" wording + GoToSocial permalink format' (#10) from feat/fediverse-link-and-gts-url into main
Reviewed-on: #10
2026-05-26 14:09:05 +00:00
.forgejo/workflows feat: generic "Fediverse" wording + GoToSocial permalink format 2026-05-26 09:07:55 -05:00
assets feat: generic "Fediverse" wording + GoToSocial permalink format 2026-05-26 09:07:55 -05:00
exampleSite feat: generic "Fediverse" wording + GoToSocial permalink format 2026-05-26 09:07:55 -05:00
i18n feat: generic "Fediverse" wording + GoToSocial permalink format 2026-05-26 09:07:55 -05:00
layouts/partials feat: generic "Fediverse" wording + GoToSocial permalink format 2026-05-26 09:07:55 -05:00
proxy ci: build proxy image with ko instead of Docker Buildx 2026-05-25 23:39:24 -05:00
.gitignore feat: ActivityPub (Mastodon) comments Hugo Module 2026-05-25 08:25:59 -05:00
CLAUDE.md ci: build proxy image with ko instead of Docker Buildx 2026-05-25 23:39:24 -05:00
go.mod chore: rename module/repo to hugo-ap-comments 2026-05-25 08:35:45 -05:00
hugo.toml feat: generic "Fediverse" wording + GoToSocial permalink format 2026-05-26 09:07:55 -05:00
LICENSE feat: ActivityPub (Mastodon) comments Hugo Module 2026-05-25 08:25:59 -05:00
README.md feat: generic "Fediverse" wording + GoToSocial permalink format 2026-05-26 09:07:55 -05:00

hugo-ap-comments

CI

Embed Mastodon / Fediverse replies as a comment section on your static Hugo site — no backend, no database, no third-party tracker.

When you publish a post, you toot a link to it. Readers click Load comments and their browser fetches the replies to that toot straight from the Mastodon API and renders them as a threaded discussion. Based on Carl Schwan's approach.

Features

  • Distributed as a Hugo Module — one import, one partial call.
  • Click-to-load: zero network requests until the reader asks for comments.
  • Remote HTML sanitized with DOMPurify (bundled, no CDN).
  • Threaded replies, original-poster badge, custom-emoji rendering.
  • Assets built, minified and fingerprinted (with SRI) via Hugo Pipes.
  • Namespaced (ap-) CSS and i18n strings you can override/translate.

Requirements

  • Hugo extended ≥ 0.116.0 (uses js.Build).
  • Go (for Hugo Modules).

Install

Initialise your site as a Hugo Module if you haven't already:

hugo mod init github.com/you/your-site

Add the import to your site config (hugo.toml):

[module]
  [[module.imports]]
    path = "forge.wynning.tech/james/hugo-ap-comments"

Then fetch it:

hugo mod get forge.wynning.tech/james/hugo-ap-comments

Usage

1. Call the partial from a single template, e.g. layouts/_default/single.html:

{{ partial "activitypub-comments.html" . }}

2. Set your instance and account once in site config — these are the same for every post:

# hugo.toml / config.yaml
params:
  activitypub_comments:
    host: floss.social      # the toot's instance host
    username: carlschwan    # account that posts (used for the OP badge)

3. Toot a link to your post from that account.

4. Link each post to its toot in front matter — only id is required:

comments:
  id: "109774012599031406"    # the status id (quote it — it's a big number)

The id is the long number at the end of the toot's URL (https://floss.social/@carlschwan/109774012599031406).

host and username can also be set per post (a per-post value overrides the site config), which is handy if you toot from more than one account/instance. Posts without a comments.id render nothing.

Configuration

Site-wide options under params.activitypub_comments:

Key Default Description
host Default instance host for all posts (override per post)
username Default account for all posts; used for the OP badge
maxDepth 4 Maximum indentation depth for nested replies
proxy Base URL of the comments proxy (see below); override per post
platform mastodon Permalink format: mastodon (/@user/{id}) or gotosocial (/@user/statuses/{id}); override per post

GoToSocial / instances that require auth

Mastodon serves the context endpoint for public statuses without a token, so the browser can fetch it directly. GoToSocial rejects anonymous /api/v1/... requests with 401. Run the bundled proxy/ — it holds a bearer token server-side, adds CORS, and forwards only the context endpoint — then point the widget at it:

params:
  activitypub_comments:
    host: social.example.org      # still used for display links
    username: you
    proxy: https://comments.example.com
    platform: gotosocial          # so "Reply" links use /@user/statuses/{id}

With proxy set, the browser fetches https://comments.example.com/api/v1/statuses/{id}/context instead of hitting the instance directly; the "Reply on…" link and avatars still use host. See proxy/README.md for deployment.

Theming

All styles are namespaced with ap- and driven by CSS custom properties. Override them in your own stylesheet:

.ap-comments {
  --ap-accent: #ff5500;
  --ap-indent: 1rem;
}

Localisation

Copy i18n/en.toml from this module into your site's i18n/ directory and translate the strings, or add a file for another language code.

How it works

The browser calls https://{host}/api/v1/statuses/{id}/context, reads the descendants array (public replies), sorts them into a tree by in_reply_to_id, sanitizes each with DOMPurify, and appends them to the list. Only replies that are public or unlisted are returned by the API, so direct/private messages never appear.

Local development

cd exampleSite
hugo server

The example site uses a replace directive to point the module import at the repository root, so changes are picked up without publishing.

License

MIT