Skip to content

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.

5 min setup No dependencies
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

Step 1 — Create the include

Create _includes/echothread.njk (for Nunjucks, the Eleventy default):

_includes/echothread.njk <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:

_includes/layouts/post.njk --- 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.
Recommendation: 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

_includes/echothread.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

_includes/echothread.hbs <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"
Eleventy data cascade: You can set theme values in a directory data file (e.g. 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:

_includes/layouts/post.njk (updated) {% 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 localhost for 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