| Solution | Hosting | Best For | Moderation | Pricing |
|---|
| Disqus | Hosted | General blogs | Excellent | Free/Paid |
| Commento | Self-hosted/Cloud | Privacy-focused | Good | Paid |
| Utterances | GitHub | Developer blogs | GitHub-based | Free |
| Custom | Self-hosted | Full control | Build your own | Varies |
| Giscus | GitHub Discussions | Open source projects | Community | Free |
Disqus is one of the most popular third-party commenting platforms, offering robust moderation tools and social features.
Setup
- Create a Disqus account and register your site
- Get your Disqus shortname from your site settings
- Add the Disqus embed code to your blog post template
Add a Component to your Page Type for Disqus configuration:
| Field Name | Field Type | Purpose |
|---|
enable_comments | Checkbox | Toggle comments on/off per page |
disqus_identifier | Short Text | Unique identifier (often page slug) |
disqus_url | Short Text | Canonical URL for the discussion |
Utterances uses GitHub Issues as a comment backend, making it ideal for developer-focused blogs and open-source projects.
Setup
- Install the Utterances GitHub App on your repository
- Create a Component with the configuration fields
- Add the embed script to your template
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.
Create a Collection called Comments with these fields:
| Field Name | Field Type | Purpose |
|---|
post_slug | Short Text | Links comment to a specific post |
author_name | Short Text | Commenter’s display name |
author_email | Short Text | Commenter’s email (for gravatar) |
comment_body | Long Text | The comment content |
parent_comment_id | Short Text | For threaded replies |
approved | Checkbox | Moderation status |
created_at | Date & Time | When the comment was submitted |
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:
| Field | Type | Purpose |
|---|
author_name | Short Text | Submitter’s name |
company | Short Text | Company/organization |
testimonial | Long Text | The testimonial content |
rating | Number | Star rating (1-5) |
approved | Checkbox | Moderation status |
featured | Checkbox | Show on homepage |
Q&A / FAQ section
Build a community-driven FAQ:
| Field | Type | Purpose |
|---|
question | Short Text | The question |
answer | Long Text | The answer |
category | Reference | Link to FAQ categories |
helpful_votes | Number | Upvote count |
submitted_by | Short Text | Question author |
User-generated content gallery
Showcase user submissions:
| Field | Type | Purpose |
|---|
title | Short Text | Submission title |
description | Long Text | Description |
image | Media | Uploaded image |
author_name | Short Text | Creator’s name |
featured | Checkbox | Feature on gallery |
Best practices
Moderation
- Pre-moderation - Review comments before publishing
- Spam filtering - Use services like Akismet
- Community guidelines - Publish clear commenting rules
- Report system - Allow users to flag inappropriate content
- Lazy loading - Load comments after main content
- Pagination - Don’t load all comments at once
- Caching - Cache approved comments
Privacy & compliance
- Data collection notice - Inform users what data is collected
- Email privacy - Don’t display emails publicly
- Right to deletion - Allow users to request comment removal
- GDPR compliance - Include consent checkboxes
Integrating in your app
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>
);
};
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}
/>
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>
);
};