Skip to main content

Commenting solutions overview

SolutionHostingBest ForModerationPricing
DisqusHostedGeneral blogsExcellentFree/Paid
CommentoSelf-hosted/CloudPrivacy-focusedGoodPaid
UtterancesGitHubDeveloper blogsGitHub-basedFree
CustomSelf-hostedFull controlBuild your ownVaries
GiscusGitHub DiscussionsOpen source projectsCommunityFree

Disqus integration

Disqus is one of the most popular third-party commenting platforms, offering robust moderation tools and social features.

Setup

  1. Create a Disqus account and register your site
  2. Get your Disqus shortname from your site settings
  3. Add the Disqus embed code to your blog post template

Creating a Disqus Component

Add a Component to your Page Type for Disqus configuration:
Field NameField TypePurpose
enable_commentsCheckboxToggle comments on/off per page
disqus_identifierShort TextUnique identifier (often page slug)
disqus_urlShort TextCanonical URL for the discussion

Utterances (GitHub-based comments)

Utterances uses GitHub Issues as a comment backend, making it ideal for developer-focused blogs and open-source projects.

Setup

  1. Install the Utterances GitHub App on your repository
  2. Create a Component with the configuration fields
  3. Add the embed script to your template

Building custom comments with Collections

For full control over your comments system, you can build a custom solution using ButterCMS Collections and the Write API.
This approach requires a backend server to handle comment submissions and moderation. The ButterCMS Write API requires authentication with a Write API token (not the Read API token) and should never be called directly from the client—always use a backend server.

Comment Collection schema

Create a Collection called Comments with these fields:
Field NameField TypePurpose
post_slugShort TextLinks comment to a specific post
author_nameShort TextCommenter’s display name
author_emailShort TextCommenter’s email (for gravatar)
comment_bodyLong TextThe comment content
parent_comment_idShort TextFor threaded replies
approvedCheckboxModeration status
created_atDate & TimeWhen the comment was submitted

Community features

Beyond comments, you can use ButterCMS Collections to build various community features:

User testimonials Collection

Allow users to submit testimonials that can be moderated and displayed:
FieldTypePurpose
author_nameShort TextSubmitter’s name
companyShort TextCompany/organization
testimonialLong TextThe testimonial content
ratingNumberStar rating (1-5)
approvedCheckboxModeration status
featuredCheckboxShow on homepage

Q&A / FAQ section

Build a community-driven FAQ:
FieldTypePurpose
questionShort TextThe question
answerLong TextThe answer
categoryReferenceLink to FAQ categories
helpful_votesNumberUpvote count
submitted_byShort TextQuestion author
Showcase user submissions:
FieldTypePurpose
titleShort TextSubmission title
descriptionLong TextDescription
imageMediaUploaded image
author_nameShort TextCreator’s name
featuredCheckboxFeature on gallery

Best practices

Moderation

  1. Pre-moderation - Review comments before publishing
  2. Spam filtering - Use services like Akismet
  3. Community guidelines - Publish clear commenting rules
  4. Report system - Allow users to flag inappropriate content

Performance

  1. Lazy loading - Load comments after main content
  2. Pagination - Don’t load all comments at once
  3. Caching - Cache approved comments

Privacy & compliance

  1. Data collection notice - Inform users what data is collected
  2. Email privacy - Don’t display emails publicly
  3. Right to deletion - Allow users to request comment removal
  4. GDPR compliance - Include consent checkboxes

Integrating in your app

Disqus integration (React)

import { useEffect } from 'react';

const DisqusComments = ({ identifier, url, title }) => {
  useEffect(() => {
    if (window.DISQUS) {
      window.DISQUS.reset({
        reload: true,
        config: function() {
          this.page.identifier = identifier;
          this.page.url = url;
          this.page.title = title;
        }
      });
      return;
    }

    window.disqus_config = function() {
      this.page.url = url;
      this.page.identifier = identifier;
      this.page.title = title;
    };

    const script = document.createElement('script');
    script.src = 'https://YOUR_SHORTNAME.disqus.com/embed.js';
    script.setAttribute('data-timestamp', +new Date());
    document.body.appendChild(script);

    return () => {
      const disqusThread = document.getElementById('disqus_thread');
      if (disqusThread) {
        while (disqusThread.hasChildNodes()) {
          disqusThread.removeChild(disqusThread.lastChild);
        }
      }
    };
  }, [identifier, url, title]);

  return (
    <div className="comments-section">
      <h3>Comments</h3>
      <div id="disqus_thread" />
      <noscript>
        Please enable JavaScript to view the comments.
      </noscript>
    </div>
  );
};

export default DisqusComments;

Usage with ButterCMS Blog Posts

const BlogPost = ({ post }) => {
  const commentsEnabled = post.fields.enable_comments;
  const pageUrl = `https://yoursite.com/blog/${post.slug}`;

  return (
    <article>
      <h1>{post.fields.title}</h1>
      <div dangerouslySetInnerHTML={{ __html: post.fields.body }} />

      {commentsEnabled && (
        <DisqusComments
          identifier={post.slug}
          url={pageUrl}
          title={post.fields.title}
        />
      )}
    </article>
  );
};

Utterances (GitHub-based comments)

import { useEffect, useRef } from 'react';

const UtterancesComments = ({ repo, issueTerm }) => {
  const containerRef = useRef(null);

  useEffect(() => {
    const script = document.createElement('script');
    script.src = 'https://utteranc.es/client.js';
    script.setAttribute('repo', repo);
    script.setAttribute('issue-term', issueTerm);
    script.setAttribute('theme', 'github-light');
    script.setAttribute('crossorigin', 'anonymous');
    script.async = true;

    containerRef.current.appendChild(script);

    return () => {
      if (containerRef.current) {
        containerRef.current.innerHTML = '';
      }
    };
  }, [repo, issueTerm]);

  return <div ref={containerRef} className="utterances-container" />;
};

// Usage
<UtterancesComments
  repo="yourname/yourrepo"
  issueTerm={post.slug}
/>

Custom comments with Collections

Use the ButterCMS Write API from a backend server only. Never expose Write API tokens in client code.

Backend API (Node.js/Express)

const express = require('express');
const Butter = require('buttercms');

const butter = Butter('YOUR_WRITE_API_TOKEN');
const router = express.Router();

// Fetch comments for a post
router.get('/api/comments/:postSlug', async (req, res) => {
  try {
    const response = await butter.content.retrieve(['comments'], {
      'fields.post_slug': req.params.postSlug,
      'fields.approved': true,
      order: '-fields.created_at'
    });

    res.json(response.data.data.comments);
  } catch (error) {
    res.status(500).json({ error: 'Failed to fetch comments' });
  }
});

// Submit a new comment
router.post('/api/comments', async (req, res) => {
  const { postSlug, authorName, authorEmail, commentBody, parentId } = req.body;

  if (!postSlug || !authorName || !commentBody) {
    return res.status(400).json({ error: 'Missing required fields' });
  }

  try {
    await butter.content.create('comments', {
      post_slug: postSlug,
      author_name: authorName,
      author_email: authorEmail,
      comment_body: commentBody,
      parent_comment_id: parentId || null,
      approved: false,
      created_at: new Date().toISOString()
    });

    res.json({
      success: true,
      message: 'Comment submitted for moderation'
    });
  } catch (error) {
    res.status(500).json({ error: 'Failed to submit comment' });
  }
});

module.exports = router;

Frontend component

import { useState, useEffect } from 'react';

const CustomComments = ({ postSlug }) => {
  const [comments, setComments] = useState([]);
  const [newComment, setNewComment] = useState({ name: '', email: '', body: '' });
  const [submitted, setSubmitted] = useState(false);

  useEffect(() => {
    fetchComments();
  }, [postSlug]);

  const fetchComments = async () => {
    const response = await fetch(`/api/comments/${postSlug}`);
    const data = await response.json();
    setComments(data);
  };

  const handleSubmit = async (e) => {
    e.preventDefault();

    const response = await fetch('/api/comments', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({
        postSlug,
        authorName: newComment.name,
        authorEmail: newComment.email,
        commentBody: newComment.body
      })
    });

    if (response.ok) {
      setSubmitted(true);
      setNewComment({ name: '', email: '', body: '' });
    }
  };

  return (
    <section className="comments">
      <h3>Comments ({comments.length})</h3>

      <div className="comment-list">
        {comments.map(comment => (
          <div key={comment.meta.id} className="comment">
            <div className="comment-header">
              <strong>{comment.author_name}</strong>
              <time>{new Date(comment.created_at).toLocaleDateString()}</time>
            </div>
            <p>{comment.comment_body}</p>
          </div>
        ))}
      </div>

      {submitted ? (
        <p className="success">Thanks! Your comment is awaiting moderation.</p>
      ) : (
        <form onSubmit={handleSubmit}>
          <input
            type="text"
            placeholder="Name"
            value={newComment.name}
            onChange={(e) => setNewComment({ ...newComment, name: e.target.value })}
          />
          <input
            type="email"
            placeholder="Email"
            value={newComment.email}
            onChange={(e) => setNewComment({ ...newComment, email: e.target.value })}
          />
          <textarea
            placeholder="Comment"
            value={newComment.body}
            onChange={(e) => setNewComment({ ...newComment, body: e.target.value })}
          />
          <button type="submit">Submit</button>
        </form>
      )}
    </section>
  );
};