How to add comments to an Eleventy site
Add comments to an Eleventy (11ty) site with a Nunjucks, Liquid, or Handlebars include. Per-collection toggles, fileSlug threads, no plugins.
On this page
Why Eleventy + EchoThread
Eleventy is the "zero-config" static site generator that lets you use whatever template language you prefer. It's fast, flexible, and intentionally simple. EchoThread shares that philosophy: a single script tag, zero dependencies, no build plugins, no configuration files.
The widget is under 20 KB gzipped and loads asynchronously from a CDN, so it won't affect your site's build time or performance score.
Prerequisites
- An Eleventy site (any version)
- An EchoThread account — create one free
- Your site's API key from the EchoThread dashboard
Step 1 — Create the include
Create _includes/echothread.njk (for Nunjucks, the Eleventy default):
<div id="echothread"
data-api-key="YOUR_API_KEY"
data-page-url="{{ page.url | url }}"
data-identifier="{{ page.fileSlug }}"
data-page-title="{{ title }}"></div>
<script src="https://cdn.echothread.io/widget.js" async></script>
Step 2 — Add to your layout
Open your blog post layout (e.g. _includes/layouts/post.njk) and add the include after the content:
---
layout: layouts/base.njk
---
<article>
<h1>{{ title }}</h1>
{{ content | safe }}
<!-- Comments -->
{% include "echothread.njk" %}
</article>
That's it. Comments will appear on every page using this layout.
Choosing an identifier
The data-identifier keeps comments attached to the right page even if URLs change. Eleventy provides these variables:
{{ page.fileSlug }}— the filename without extension, e.g.my-first-post. Stable and human-readable.{{ page.url }}— the full URL path, e.g./blog/my-first-post/.{{ page.inputPath }}— the source file path, e.g../src/blog/my-first-post.md.
page.fileSlug is the best default. It's stable, readable, and doesn't change when you restructure your URL permalinks.
Other template languages
Eleventy supports multiple template engines. The HTML output is identical — only the include syntax differs:
Liquid
<div id="echothread"
data-api-key="YOUR_API_KEY"
data-page-url="{{ page.url }}"
data-identifier="{{ page.fileSlug }}"
data-page-title="{{ title }}"></div>
<script src="https://cdn.echothread.io/widget.js" async></script>
Include it with: {% include 'echothread.liquid' %} or {% render 'echothread.liquid' %}
Handlebars
<div id="echothread"
data-api-key="YOUR_API_KEY"
data-page-url="{{ page.url }}"
data-identifier="{{ page.fileSlug }}"
data-page-title="{{ title }}"></div>
<script src="https://cdn.echothread.io/widget.js" async></script>
Include it with: {{> echothread}}
Theming
Control the widget's appearance with data-theme and data-accent-color:
<!-- Dark mode -->
data-theme="dark"
<!-- Custom background -->
data-theme="#f5f0eb"
<!-- Custom accent -->
data-accent-color="#0ea5e9"
blog/blog.json) and reference them in the include with {{ echothread_theme }}. This keeps your config DRY across all posts in that collection.
Disable per page
Wrap the include in a conditional:
{% if comments != false %}
{% include "echothread.njk" %}
{% endif %}
Then set comments: false in any post's front matter to hide the widget.
Troubleshooting
Comments don't appear
- Verify the API key in your dashboard.
- Check the domain matches (including
localhostfor local dev). - Look for JavaScript errors in the browser console.
Variables don't render
Make sure the include file extension matches your template language (.njk for Nunjucks, .liquid for Liquid, etc.). Eleventy processes includes using the same engine as the parent template by default.
Widget doesn't match my theme
Use data-theme with a hex color or override CSS custom properties. See the theming docs for all available variables.
Ready to add comments to your Eleventy (11ty) site?
Free during beta. Set up in under 5 min.
Create free account