FAQ

Does StaticLayer host my comments?

No. The runtime is deployed in your Cloudflare environment. There is no centralized comment database.

Do visitors need an account?

No. Visitors just write a nickname and a comment.

Do visitors need to provide an email?

No.

What can visitors write in a comment?

Plain text and emoji. The widget renders with textContent only: no HTML, no Markdown, no clickable links. Nickname is capped at 50 characters, body at 3000 characters. Anything pasted is displayed literally as text — this is a deliberate XSS boundary, not a limitation.

Does the widget use visitor tracking cookies?

No. The public widget does not use visitor tracking cookies, local storage, fingerprinting, or analytics.

Can I use it with GitHub Pages?

Yes, using the Worker as the API endpoint and configuring the necessary origin/CORS settings.

Does it work with Astro / Hugo / Jekyll / Next?

Yes for static output, using the framework-agnostic widget.

Is it open source?

Source-available. The code is public under the Elastic License 2.0 (ELv2) — you can read, modify, self-host and contribute, but you may not resell it or offer it to third parties as a hosted/managed service. The owner may grant separate commercial licenses.

Is everything free forever?

Self-hosting the current core is free under ELv2 and you can keep using the version you have. Future premium features (multi-site dashboard, advanced moderation, up/down ratings, white-labeling, hosted installer) may ship in a separate proprietary package, and the owner can grant commercial licenses. The license also lets the owner decide the licensing of future releases — nothing you self-host today is taken away, but resale rights stay with the owner. See Terms.

Is it GDPR compliant automatically?

No. StaticLayer is designed to minimize data processing, but site owners remain responsible for their own legal compliance.

How are comments seen by search engines?

Honestly: comments are rendered client-side by the widget, so the raw HTML sent to crawlers does not contain comment text — they live in your D1 database and are fetched via JavaScript. Search engines that execute JavaScript may render them at their discretion, but you should not rely on comments for SEO. They are for readers, not for ranking.

How does the anti-bot / anti-spam technology work?

Five layers: (1) a server-issued, HMAC-signed, single-use challenge; (2) Proof-of-Work — the visitor's browser must compute a SHA-256 nonce with a server-controlled difficulty before the comment is accepted; (3) a hidden honeypot field — if filled, the submission is silently dropped (no data read or stored); (4) a 3-second time gate — submissions faster than 3s after the challenge are rejected with 429 (issue time recovered from the signed challenge, zero server state); (5) edge rate limiting (route-based, never by IP) plus human moderation. No CAPTCHA, no third-party anti-bot service, no tracking.

Can a visitor post more than one comment?

Yes. Each submission requires a fresh Proof-of-Work challenge, so there is no account or login. Comments are rate-limited per route and every comment still enters moderation before it is published.

Is the widget customizable?

Yes. The widget is plain CSS with design tokens (--accent, --bg, --radius, etc.) so you can restyle it to match your site — see the theme switcher in the demo. Runtime knobs (POW_DIFFICULTY, rate limits, ALLOWED_ORIGINS) are plain vars in your config, and every thread is keyed per page.

Can I have comments on some pages but not others?

Yes — that is the default. Only pages that include the widget snippet get comments. Each page (or data-article-id) has its own thread, so you decide page by page.

Where should I place comments and reactions on my page?

The widget renders exactly where you put the <div data-staticlayer> in your HTML. Best practice: comments right after the article content (before the footer) in your content column; reactions higher, under the headline or just above the comments. Only pages with the snippet get a widget — you decide page by page. See Integrations → Where to place it.

Can I show reactions without comments, or place them separately?

Yes. Comments and reactions are independent: use comments only (no data-reactions), reactions only (data-reactions-only), or both in one element. Since each host element is independent, you can put reactions under the headline and comments at the end of the article on the same page — one widget.js, two elements.

Do I need to edit the embed code on every page?

No. When data-article-id is omitted it defaults to window.location.pathname, so the exact same snippet works on every page — put it once in your layout/template. Only set data-article-id explicitly for stable keys (routing, translations, URL changes).

Can the admin delete already-published comments?

Yes. The admin console has a Published comments section where you can Unapprove (send a comment back to the moderation queue) or Delete any public comment. Deleted comments are removed from the database and disappear from the page.

Does the installer require the terminal?

No. The hosted installer runs entirely in the browser: open staticlayer-installer.staticlayer.workers.dev ↗ (linked from the install page), connect your Cloudflare (OAuth or API token), and it deploys Worker + D1 + secrets into your account, then revokes the access token. A local variant (npm run dev:installer) exists too.

Why does the installer open a local app — can't it run from GitHub Pages?

Cloudflare's OAuth requires the client_secret_post token exchange, so a confidential server must hold the OAuth app secret. A static GitHub Pages page cannot hold that secret safely (it would be public), and Cloudflare's OAuth does not support the secret-less PKCE flow. The local app is that confidential server: it runs on your machine, the secret never leaves it, and the Cloudflare token is revoked after deploy. Prefer no terminal at all? Use the hosted installer ↗, or the manual dashboard install (~10 minutes).

Which sites can I connect to one Worker?

There is no per-site registration. Any page that embeds the widget and points at your Worker uses it; every data-article-id gets its own thread. Cross-origin calls are restricted by the ALLOWED_ORIGINS (CORS) list, so you control which site origins may talk to the Worker. Prefer isolation? Deploy a dedicated Worker per site.

Can I see which pages have comments?

Yes. After signing in, the admin console (/admin.html) shows a "Pages with comments" panel: every article path with total / pending / approved counts, and each queue item shows the page it belongs to. Try it in the demo — the admin view lists pages while visitors only see their own page's thread.

What's the difference between comments, reactions and polls?

Comments are the core: plain-text replies in per-page threads (up to 3 nested levels), every one passing through a moderation queue (approve / unapprove / delete). Reactions are one-tap anonymous emoji counts: each click solves a Proof-of-Work, difficulty escalates per page, and there is no moderation and no identity. Polls are StrawPoll-style votes (2–10 options) created in the admin, PoW-protected, optionally with an anonymous one-vote-per-browser guard. Up/down ratings are planned on the same engine and not yet shipped. All share the same privacy model — anonymous events in your Cloudflare account, no cookies, no IP.

Can I choose which reactions to show?

Yes. Per widget, list them in data-reactions="👍,❤️,🎉" (or StaticLayer.mount(el, { reactions: […] })); server-side, REACTION_OPTIONS is the allowlist (anything not listed is rejected). In the demo you can edit the list live.

Can I add reactions or polls?

Yes — both are built in. Add data-reactions="👍,❤️,🎉" to the widget to show an anonymous, PoW-protected reaction bar, fully themeable and customizable (allowed set, difficulty curve, rate limits). Create polls in the admin (Polls tab) and embed them with data-poll-id — see Integrations → Polls. Up/down ratings are on the roadmap but not yet shipped.

How do reactions prevent abuse?

By cost, not identity: every reaction solves a single-use Proof-of-Work, reactions are rate-limited per article, a minimum interval is enforced, and difficulty escalates as an article accumulates reactions. This makes stuffing expensive but cannot detect the same person voting twice — that would require tracking, which StaticLayer deliberately avoids.

How do I log in to the admin panel?

Go to https://<your-worker>/admin.html and enter your admin password (ADMIN_SECRET), or use "Sign in with GitHub" once GitHub OAuth is configured — free, no credit card, no Zero Trust plan. (Cloudflare Access "Sign in with Cloudflare" is also supported.) If you installed with the wizard, it showed the password exactly once at the end (with a copy button) — save it; if you used the CLI, set your own. The login is timing-safe and rate-limited; on success the panel sets a short-lived, HMAC-signed session cookie (__Host-StaticLayerSession — Secure, HttpOnly, SameSite=Strict) and every mutation also requires a session-bound CSRF token. Lost the password? With GitHub sign-in configured, just click "Sign in with GitHub". Otherwise rotate it: npx wrangler secret put ADMIN_SECRET (you choose the new value) — re-running the installer preserves existing secrets by design. The console is organized in tabs — Queue, Published, Pages, Lists, Settings, Updates — with search, filters, pagination and bulk actions.

Can I sign in to the admin without a password?

Yes — with GitHub OAuth, built in and free: create a GitHub OAuth App (Settings → Developer settings → OAuth Apps; the step-by-step guide is in your admin console → Settings → Admin access & sign-in), then set GITHUB_CLIENT_ID, GITHUB_CLIENT_SECRET (as a secret) and GITHUB_ADMIN_IDS (your GitHub numeric id) on the Worker. The admin login then shows "Sign in with GitHub" and only the GitHub accounts you allowlist can open the console — no password to remember. Privacy: GitHub sees only your own login (the identity provider you chose); the token is used once to read your id and then discarded — never stored, and no visitor data ever leaves your Worker. The password path stays as a fallback. (Cloudflare Access — "Sign in with Cloudflare" — is also supported.)

Can I block words or phrases in comments?

Yes. The admin Lists tab has a blocked terms (word blacklist): any comment whose body contains one of those terms (case-insensitive) is auto-rejected and never stored — it never even reaches the queue. You can also ban nicknames (blocklist) and auto-approve trusted members (allowlist).

Is there a built-in update checker?

Yes. The admin has an Updates tab that checks the latest StaticLayer release against your running version and, if a newer one exists, gives you a one-click link to the hosted installer — which re-deploys the latest version into your account preserving your secrets (including the admin password).

Do updates roll out to my site automatically?

Honestly, no — the runtime (Worker) is deployed by you into your own Cloudflare account (BYOC). New releases of widget.js and the runtime reach your site only when you redeploy your Worker; there is no automatic update channel yet. What does update automatically: this site, the docs, and the hosted installer. The protocol is versioned (schema v3), so older deployments keep working with newer tooling and vice-versa.

Limitations — know what you're getting

  • Cloudflare is still the infrastructure provider. Its network handles connection metadata, and its service availability and limits apply.
  • Proof-of-Work is not absolute spam prevention. It raises the cost of automated submissions; moderation is the final gate.
  • Comments may contain personal information supplied voluntarily by visitors (nickname, body).
  • You are responsible for configuration, moderation and your own privacy/legal notices.
  • Third-party static hosting may require CORS configuration when the Worker is a separate origin.
  • Comments are client-side rendered. The raw HTML sent to crawlers does not include comment text; comments are loaded via JavaScript from your D1. Not designed for SEO, and no SEO promise is made.
  • Remote D1 concurrency is documented as pending empirical validation before commercial launch.

Troubleshooting

  • Challenge expired — get a new challenge (the widget does this automatically).
  • Challenge already used — start a new submission.
  • CORS rejected — configure the site origin in the Worker allowlist.
  • Worker unavailable — check deployment, custom domain or routing.
  • D1 unavailable — check the binding and the database.
  • Admin session expired — sign in again.