Fetch and render Webmention.io mentions as static HTML at Jekyll build time. No JavaScript, no runtime API calls — pure static output.
During jekyll build, the plugin:
- Fetches all webmentions for your domain from the Webmention.io API.
- Groups them by target URL and stores them in
site.data["webmentions"]. - Injects matching mentions into each post's
page.webmentionsarray. - Caches the API response locally so repeated builds don't hammer the API.
Add to your site's Gemfile:
gem "jekyll-webmentions-static"And to _config.yml:
plugins:
- jekyll-webmentions-staticThen run:
bundle installAdd to _config.yml:
webmentions:
token: YOUR_TOKEN # or use WEBMENTION_TOKEN env var (preferred for CI/CD)
cache: true # default: true — cache API responses locally
cache_ttl: 3600 # default: 3600 — seconds before the cache is considered stale- Go to webmention.io and sign in with your domain (using GitHub, email
rel=me, or another IndieAuth provider). - Once authenticated, your API token is shown in your settings.
- Add the token to your
_config.ymlor (strongly preferred) set it as an environment variable:
export WEBMENTION_TOKEN=your-token-hereThe environment variable always takes precedence over the config file value. This keeps secrets out of version control.
Webmention.io needs to know your site is using it. Add these two tags to your HTML <head>:
<link rel="webmention" href="https://webmention.io/yourdomain.com/webmention" />
<link rel="pingback" href="https://webmention.io/xmlrpc" />Place {% webmentions %} anywhere in a post or page layout to render a minimal, accessible webmentions section:
{% webmentions %}This renders likes, reposts, and bookmarks as counts, and replies as individual cards with author name, avatar, and content.
For full control over markup, iterate over page.webmentions directly:
{% if page.webmentions %}
<section class="webmentions">
{% assign likes = page.webmentions | where: "type", "like-of" %}
{% assign replies = page.webmentions | where: "type", "in-reply-to" %}
{% if likes.size > 0 %}
<p>{{ likes.size }} like{{ likes.size | plural: "", "s" }}</p>
{% endif %}
{% for mention in replies %}
<article>
<strong>{{ mention.author.name }}</strong>
{% if mention.content.text %}
<p>{{ mention.content.text }}</p>
{% endif %}
<a href="{{ mention.url }}">{{ mention.published | date: "%B %-d, %Y" }}</a>
</article>
{% endfor %}
</section>
{% endif %}The full hash is available at site.data.webmentions:
{% assign mentions = site.data.webmentions[page.url | prepend: site.url] %}The plugin writes fetched data to _webmentions_cache.json in your site source directory. Subsequent builds within the TTL window (default: 1 hour) read from this file instead of calling the API.
Add the cache file to your .gitignore:
_webmentions_cache.json
To force a fresh fetch, delete the cache file or set cache: false in your config.
Store your token as a repository secret named WEBMENTION_TOKEN, then expose it in your workflow:
- name: Build Jekyll site
run: bundle exec jekyll build
env:
WEBMENTION_TOKEN: ${{ secrets.WEBMENTION_TOKEN }}Because the cache file is in .gitignore, each CI build fetches fresh data. To cache across CI runs, add _webmentions_cache.json to your Actions cache key. Without CI caching you'll make one API call per build, which is fine for most sites.
Webmentions only update when your site is rebuilt. If your site doesn't publish frequently, add a schedule trigger to your GitHub Pages workflow so mentions stay current automatically:
on:
push:
branches: [main]
schedule:
- cron: '0 */6 * * *' # rebuild every 6 hoursAdjust the cron interval to taste — daily (0 0 * * *) is reasonable for most sites. Each scheduled run fetches any new mentions from Webmention.io and publishes the updated site.
type |
Meaning |
|---|---|
like-of |
Someone liked your post |
repost-of |
Someone reposted / boosted it |
in-reply-to |
A reply with content |
bookmark-of |
Someone bookmarked it |
mention-of |
A generic link mention |
The {% webmentions %} tag outputs unstyled semantic HTML. All elements use BEM class names:
/* Outer section */
.webmentions { }
/* Reaction count pills */
.webmentions__counts { }
.webmentions__likes { }
.webmentions__reposts { }
.webmentions__bookmarks { }
/* Replies section */
.webmentions__replies { }
.webmentions__replies-heading { }
/* Individual reply card */
.webmention__reply { }
.webmention__author { }
.webmention__avatar { } /* circular avatar img */
.webmention__author-name { } /* linked name */
.webmention__date { }
.webmention__content { }The count pills include a title attribute (e.g. title="2 likes") for native browser tooltip text on hover.
- Ruby >= 2.7
- Jekyll >= 4.0
- No additional gems required — uses Ruby's built-in
net/http
MIT © 2025 Jason Chance