Skip to content

How to add comments to a Drupal site

Add EchoThread comments to Drupal 10 or 11 with a Twig template override. Replace core comments, key threads to the node ID, and avoid the text-format filter trap.

10 min setup No dependencies
On this page

Why Drupal + EchoThread

Drupal's core Comment module works, but it puts the whole burden on you: every comment lands in your database, every spammer is your problem, and moderation happens in an admin queue that was designed in a different decade. Sites that grow past a trickle of comments usually end up bolting on antispam modules and a cron job.

EchoThread moves all of that off your server. Storage, threading, reactions, ML spam filtering, and the moderation queue are hosted; Drupal only outputs a container div. The widget is under 60 KB gzipped and loads asynchronously, and because the markup it emits is static, your node stays fully cacheable.

Prerequisites

  • Drupal 10 or 11, with a theme you control (a custom theme or a sub-theme — not a core theme)
  • An EchoThread account — create one free
  • Your site's API key from the EchoThread dashboard

Don't paste the snippet into a body field or a custom block. Drupal runs that text through a text format, and the editor strips <script> tags on save — even with Full HTML selected. Twig templates are never passed through a text format, which is why the recipe below is a template override.

Step 1 — Create the template override

In your theme, create templates/node--article--full.html.twig. Drupal picks the most specific matching template name, so this file applies to article nodes viewed as a full page and nothing else.

templates/node--article--full.html.twig <article{{ attributes }}> {{ title_prefix }} {% if label and not page %} <h2{{ title_attributes }}><a href="{{ url }}" rel="bookmark">{{ label }}</a></h2> {% endif %} {{ title_suffix }} <div{{ content_attributes }}> {{ content|without('comment') }} </div> <div id="echothread" data-api-key="YOUR_API_KEY" data-page-url="{{ url }}" data-identifier="node-{{ node.id }}" data-page-title="{{ node.label }}"></div> <script src="https://cdn.echothread.io/widget.js" async></script> </article>

Replace YOUR_API_KEY with the public API key from your EchoThread dashboard. It is designed to live in page source — it only lets visitors read and post on your registered site.

Two details are worth calling out, because the obvious version of each is wrong:

  • {{ node.label }}, not {{ label }}. In a node template label is a render array, and Drupal renders it as <span>Title</span> — markup inside an HTML attribute. node.label is the plain string.
  • {{ content|without('comment') }} renders the node with the core comment field removed, so you do not end up with two comment sections stacked on top of each other.

{{ url }} emits a root-relative path such as /node/1. That is fine — the widget resolves a relative data-page-url against the current page before sending it.

Step 2 — Rebuild the cache

Drupal caches its template registry, so a new override is invisible until you rebuild. Use Administration → Configuration → Development → Performance → Clear all caches, or from the command line:

terminal drush cache:rebuild

Reload any article and the widget appears below the content.

Turning off core comments

The |without('comment') filter hides core comments on this template, which is enough for most sites. To switch them off properly — so Drupal stops accepting new ones and stops sending notifications — go to Structure → Content types → Article → Manage fields and either delete the Comments field or set its default to Closed.

Existing comments stay in your database either way. If you want them in EchoThread, export them and use the importer in your dashboard rather than leaving two archives.

Choosing an identifier

The snippet uses node-{{ node.id }}. The node ID never changes, so the discussion survives a URL alias change, a language switch, or a move to a different menu position — all of which would break a URL-keyed thread.

Prefix the value (node- here) if the same EchoThread site also serves another platform, so identifiers from the two cannot collide.

Other content types

Drupal's template naming is mechanical. Copy the same file under a different name to cover a different content type, or drop the type entirely to cover all of them:

  • node--page--full.html.twig — basic pages only
  • node--blog-post--full.html.twig — a custom "Blog post" type (underscores become hyphens)
  • node--full.html.twig — every content type viewed as a full page

Troubleshooting

The template change does nothing

  • Rebuild the cache — this is the cause almost every time.
  • Confirm the file is in the theme that is actually enabled as default, not in a core theme such as Olivero. Overriding a core theme's file works until the next Drupal update overwrites it.
  • Turn on Twig debugging in development.services.yml; Drupal then prints the candidate template names in the page source as HTML comments, so you can see exactly which name it is looking for.

The script tag disappeared

You pasted it into a body field, a custom block, or a text-format-backed field rather than a Twig template. Drupal's editor strips <script> on save. Use the template override.

Sign-in opens and then does nothing

Register your Drupal site's domain on the EchoThread site in the dashboard. Comments display without it, but a completed sign-in is only handed back to a registered domain.

Frequently asked questions

Why not just paste the script into a block or a body field?

Because Drupal filters it out. Body and block text runs through a text format, and the editor strips <script> on save even under Full HTML. A Twig template is not passed through a text format at all, which is why the recommended recipe is a template override rather than a content edit.

Do I need a custom module?

No. One template file in your own theme is enough. You do not need to write PHP, install a contributed module, or touch core.

Does this replace Drupal core comments?

Yes, if you use content|without('comment') as shown below — that renders everything on the node except the core comment field. You can also disable the comment field on the content type under Structure → Content types → Manage fields.

Will this slow down my Drupal site?

No. The widget is under 60 KB gzipped, loads from a CDN with async, and adds nothing to Drupal's render cache — the markup it emits is static, so the node stays cacheable.

Ready to add comments to your Drupal site?

Free for your first site. Set up in under 10 min.

Create free account