cf-comments / demo

Anonymous inline comments for Cloudflare Pages.

Drop into any static site. Select any passage on the page. Leave a comment. No login. No third-party platform. Your data in your D1. This page is wired up — try it.

Try it now

Highlight any sentence on this page. A small popup appears next to your selection — type a comment, optionally add a name, hit submit. The passage gets marked in amber, and the floating pencil button (bottom-right) shows your comment in a side panel.

Reload the page. The highlight is still there. Comments are stored in Cloudflare D1 and rendered on every visit. Other readers see what you wrote.

Note: this is a public demo with no moderation. Comments are visible to everyone. Don't paste anything sensitive. Spam will be cleared periodically.

What it is

cf-comments is an anonymous, no-login comment layer for static sites hosted on Cloudflare Pages. The whole stack is:

What it isn't

Install

Point your agent at AGENTS.md in the cf-comments repo. It's a top-to-bottom runbook with concrete commands and verification checks at every step. Roughly:

  1. Copy four files into your project (two assets, two Pages Functions)
  2. Apply the schema to a new D1 database
  3. Add three tags to your HTML shell — a CSS link, a config block, a module script
  4. Set two Pages secrets — IP_HASH_SALT and ADMIN_SECRET
  5. Deploy

The full runbook handles the edge cases. ~10 minutes if you have wrangler authed.

Configuration

One inline <script> tag, set before loading the module:

<script>
  window.__CFC_CONFIG = {
    namespace: "my-blog-comments",
    contentSelector: "article",
    apiBase: "/api/annotations",
  };
</script>

<link rel="stylesheet" href="/cf-comments.css">
<script type="module" src="/cf-comments.js"></script>

namespace prefixes localStorage keys so a user's display name on site A doesn't pre-fill on site B. contentSelector picks the element whose contents are annotatable — usually article, main, or a more specific selector. apiBase only needs changing if you've moved the Pages Functions routes from the default.

Moderation

Delete any comment via the API:

curl -X DELETE \
  -H "X-Admin-Secret: <your-secret>" \
  https://<your-site>/api/annotations/<id>

Soft-delete: the row stays in D1 with deleted = 1, hidden from the API. To hard-delete, run a SQL command via wrangler.

How the highlights work

Comments anchor to the text via the W3C Web Annotation Data Model — specifically a TextQuoteSelector with prefix and suffix context, plus a TextPositionSelector as a fallback. On page load, cf-comments walks the DOM, finds the matching quote text, and wraps it in a <mark class="df-anno-mark"> element.

The mark is inline with the text, not an overlay. That means the highlight flows naturally with the body copy, survives reflow, and looks the same to every reader on every device. It's also why multi-line quotes get split into multiple <mark> elements that all share the same data-id.

Limits worth knowing


Built by Matt Alldian. Source on GitHub. MIT license.