# Comments

A comment thread under any page of a static site — paste one snippet, moderate in the dashboard. No accounts for commenters and no third-party script.

Comments puts a thread under any page of your site. Like [forms](/docs/widgets),
it is served from your own domain: one snippet in your HTML, no third-party
script, no tracking, and nothing for a visitor to sign up for.

## Setting it up

1. **Switch it on.** In the dashboard, open the site, go to **Widgets** and turn
   on **Comments**. It is off by default; while it is off the snippet shows
   nothing and every comment is refused. Turning it off later deletes nothing.
2. **Paste the snippet** where the thread should appear, then deploy:

```html
<div id="one9x-comments"></div>
<script type="module" src="/_one9x/comments.js"></script>
```

Each page gets its own thread, named by its path — `/post`, `/post/` and
`/post/index.html` are the same thread. If a post moves, pin its thread to the
old path so the comments move with it:

```html
<div id="one9x-comments" data-page="/blog/old-url"></div>
```

The widget's elements all carry `o9c-` classes, and its own styles load first,
so any rule your site writes for them wins.

## Moderating

Open **Widgets → Comments** for the site.

- **New comments wait for your approval** by default. Turn off "Hold new
  comments for approval" and they appear at once; you can still delete them.
- **Approve, delete or reply** from the list of waiting comments, or delete from
  the published ones. **Delete all waiting** clears the whole queue at once — a
  spam wave is one click.
- **Your replies carry an author badge.** Only a reply sent from the dashboard
  (or the [API](#automate-with-the-api)) can have one — nothing a visitor posts
  can. Replying to a comment that is waiting approves it.
- **One level of replies.** A reply to a reply joins the same thread, under the
  top-level comment.
- **Comments are plain text.** Links are shown as text and never made
  clickable, which takes the point out of posting them as spam.

A visitor gives a name and the comment — no email and no account. The same
invisible proof-of-work [captcha](/docs/widgets#the-html) as forms runs on every
comment, and it is always on.

## Plans and the monthly count

Every hosting plan includes a Comments plan, and it covers all your sites
([pricing](/pricing)):

| Comments plan | Published comments a month | Included with |
| --- | --- | --- |
| Comments Free | 500 | Free, Starter |
| Comments Plus | 5,000 | Pro |
| Comments Pro | 50,000 | Business |
| Comments Max | 200,000 | — |

What counts is a comment going **live** on any of your sites during the
calendar month (Indian time). A comment you hold and never approve does not
count, and neither do your own replies.

When the month's count is used, **the comment box closes** on all your sites
until the 1st: existing comments stay up, visitors see *"Comments are closed for
now"* instead of the form, and approving waits for the new month. Nothing is
billed as overage. The Comments page in the dashboard shows the count, the
reset date and this rule before you reach it.

To raise the limit, choose **Upgrade** beside Comments on the **Billing** page,
or pick a bigger Comments plan while renewing your hosting plan, in the same
payment. On a paid hosting plan the upgrade starts at once and **ends with your
plan**: you pay its price less the Comments plan your hosting includes (Comments
Pro on Pro is ₹200 a month), for the days left, and it renews together with the
plan — one reminder, one payment. On Free it is a month of its own.

Moving up from a paid Comments plan costs the difference for the days left; a
smaller one starts when the paid one ends. Nothing renews automatically, and a
paid Comments plan ending takes nothing down — the count goes back to the plan
your hosting includes.

## Limits

| | |
| --- | --- |
| Name | 50 characters |
| Comment | 5,000 characters |
| Comments per site | 500 per rolling 24 hours, or a tenth of your Comments plan's month if that is more |
| Waiting for approval | 1,000 per site — past it, a site that holds comments takes no new ones until you catch up |
| Posts per visitor | 20 per hour, per visitor IP |
| Thread reads per visitor | 600 per minute, per visitor IP |
| Shown per page | The first 1,000 comments |

## Automate with the API

The Comments page is an HTTP API, the same one the dashboard calls. Authenticate
with an API token from the dashboard's Tokens page; `{id}` is the numeric site
id.

| Endpoint | What it does |
| --- | --- |
| `GET /api/pages/sites/{id}/comments` | Takes `status` (`pending` or `published`), `limit` and `cursor`. Returns the comments, the switches, and `usage`: `used`, `quota`, `plan`, `resets_at` |
| `PUT /api/pages/sites/{id}/comments` | Body `{"enabled": true}` and/or `{"hold": false}` |
| `POST /api/pages/sites/{id}/comments/{cid}/approve` | Publishes a waiting comment |
| `POST /api/pages/sites/{id}/comments/{cid}/reply` | Body `{"name": "…", "body": "…"}` — a reply with the author badge |
| `DELETE /api/pages/sites/{id}/comments/{cid}` | `204`. Deletes its replies too |
| `DELETE /api/pages/sites/{id}/comments?status=pending` | Deletes every waiting comment; returns `{"deleted": n}`. Published comments are never deleted in bulk |

Approving, or a reply that would publish a waiting comment, is refused with a
`403` once the month's count is used. A suspended site's comments are under the
same preservation hold as the rest of its content: every change is a `403`.

## What this is not

- **There are no email notifications.** Waiting comments are counted on the
  site's Widgets tab; poll the API if you need more.
- **Commenters cannot edit or delete** what they posted. Delete it for them.
- **There are no commenter accounts** or sign-in.
- **The thread is read once, when the page loads.** On a single-page app that
  changes the address without loading a new page, the thread stays on the first
  page's; give each route its own `data-page` and a fresh element.
- **`/docs` and `/docs/index.html` share a thread.** If your site ships both an
  exact `/docs` file and a `/docs/index.html`, they are two pages with one
  thread; pin one of them with `data-page`.