> ## Documentation Index
> Fetch the complete documentation index at: https://buttercms.com/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Community comments

> Add commenting to your ButterCMS site using Disqus, Utterances, or a custom Collections solution, with React code examples.

## Commenting solutions overview

| 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 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](https://disqus.com/) 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 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 (GitHub-based comments)

[Utterances](https://utteranc.es/) 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.

<Warning>
  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.
</Warning>

### Comment Collection schema

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   |

***

## 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:

| 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

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)

```jsx theme={null}
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

```jsx theme={null}
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)

```jsx theme={null}
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

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

#### Backend API (Node.js/Express)

```javascript theme={null}
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

```jsx theme={null}
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>
  );
};
```
