Skip to main content

Widget CDN Integration

Embed an Anonfeedback widget on any webpage with a single script tag. No framework required.

Quick start

<script
src="https://cdn.anonfeedback.io/widget/v1/widget.js"
data-app-key="YOUR_APP_KEY"
async
></script>

Replace YOUR_APP_KEY with the App ID shown after creating an app in Create → Apps.

How theming works

The widget theme (Mini or Dark) is set when you create or edit your app in the dashboard. On top of the base theme, the widget is styled by a set of design tokens you can override at three levels (strongest wins):

  1. Your page's CSS on the widget mount element
  2. Custom theme configured per-app in the dashboard (Create → Apps → Customize colors)
  3. The built-in theme preset

Available tokens

TokenControls
--af-accentPrimary accent: selected tags, submit button, focus highlights
--af-bgFeedback panel background
--af-textPanel text color
--af-radiusPanel corner radius
--af-trigger-bgFloating trigger button background
--af-trigger-radiusTrigger button corner radius
--af-fontFont stack (defaults to a system stack)
--af-zStacking z-index of the widget

Overriding tokens from your own stylesheet

The widget renders inside a shadow root, so your CSS cannot touch its internals, but design tokens deliberately inherit through. Target the mount element:

#anonfeedback-widget {
--af-accent: #0a6e4f;
--af-radius: 6px;
}

Dashboard-configured custom themes need no re-embed: token changes apply on the next widget load.

Optional script overrides

You can override settings per-page without changing the app settings:

AttributeTypeDescription
data-app-keystringRequired for auto-init. Your app's public ID. Omit it to load the script without mounting and call AnonfeedbackWidget.init() yourself.
data-positionbottom-right | bottom-left | bottom-centerOverrides the position set in the app.
data-trigger-labelstringOverrides the button label set in the app.
data-themedark | miniOverrides the theme set in the app.
data-api-urlstringAdvanced. API origin override for staging/self-hosted setups. Defaults to the production API.

Example with overrides:

<script
src="https://cdn.anonfeedback.io/widget/v1/widget.js"
data-app-key="YOUR_APP_KEY"
data-position="bottom-left"
data-trigger-label="Send feedback"
async
></script>

Programmatic control (SPAs)

Single-page apps can load the script without data-app-key and control the widget lifecycle through a global API (AnonfeedbackWidget.init() returning open/close/destroy), or bundle the same API via the @anonfeedback/widget npm package. See npm Package & Programmatic API for the full guide.

Versioning & integrity

Two kinds of URL are published:

URLMutabilityCacheUse when
/widget/v1/widget.jsUpdated on every 1.x release1 hourDefault. You want fixes and features automatically.
/widget/<version>/widget.js (e.g. /widget/1.1.3/widget.js)Immutable, never changes1 yearYou need byte-stable content, e.g. for Subresource Integrity.

Pinned versions support Subresource Integrity. SRI hashes per version are published at https://cdn.anonfeedback.io/widget/versions.json:

<script
src="https://cdn.anonfeedback.io/widget/1.1.3/widget.js"
integrity="sha384-…"
crossorigin="anonymous"
data-app-key="YOUR_APP_KEY"
async
></script>

The version in these pinned examples (currently 1.1.3) reflects the latest release and is updated alongside each CDN and npm publish. For the authoritative list of every released version and its SRI hash, see versions.json. To always get the newest build automatically, use the /widget/v1/ channel above (or @anonfeedback/widget on npm) and skip version pinning entirely.

SRI is not possible on the v1 channel URL: integrity pinning and auto-updates are mutually exclusive by design. Breaking changes will ship under a new /widget/v2/ channel; v1 embeds keep working unchanged.

Self-hosting

widget.js is a single static, environment-agnostic file. Organizations with strict content-security or vendor policies may copy it to their own origin and serve it themselves:

  1. Download a pinned version: https://cdn.anonfeedback.io/widget/<version>/widget.js
  2. Serve it from your own origin and embed it with the same data-* attributes.
  3. Allowed-origins enforcement still applies server-side, unchanged.

Note that self-hosted copies do not receive automatic v1 channel updates.

Content Security Policy

The widget injects its styles inside its own shadow root and talks only to the Anonfeedback API. A minimal CSP needs:

script-src https://cdn.anonfeedback.io; (or your own origin if self-hosting)
connect-src https://api.anonfeedback.io;

Security

App ID vs. API key

Every app has two distinct identifiers:

IdentifierWhere it livesPurpose
App ID (data-app-key)Embedded in your HTMLPublic reference, safe to ship in page source
API keyShown once at creationSecret credential for server-side use, never embed in HTML

The API key is stored as a SHA-256 hash on the server and is never returned by any API call after the initial creation response. If you lose it, rotate it from the Apps page. Rotation immediately invalidates the previous key.

Allowed origins enforcement

Every widget request (config fetch and feedback submission) is validated against your app's allowed origins list before any data is returned or stored. Requests from an origin not on the list receive a 403 Forbidden and no config or submission goes through.

  • Origins are normalized (lowercased, trailing slash stripped) on both sides of the comparison, so https://example.com/ and https://Example.com are treated the same.
  • An app with no allowed origins configured is blocked entirely. The widget will not load until at least one origin is added.
  • Wildcard origins are not supported. Each allowed origin must be an exact scheme + host (e.g. https://example.com).

Content moderation

Widget submissions pass through AI-powered content moderation before they are stored. Feedback flagged by the moderation check is silently dropped. It is never written to the database and never appears in your dashboard.

Key rotation

If a key is ever exposed, rotate it immediately from Create → Apps → your app → Rotate key. Rotation generates a new 256-bit random key and invalidates the previous one instantly. The widget itself does not use the API key (it uses only the public App ID), so rotation does not require any HTML change.

Rate limits

Both widget endpoints (/config fetch on load and /submit on feedback submission) share a single per-IP rate limit:

LimitWindowApplies to
120 requests1 minuteEach unique client IP

This is per-IP, not per-app, so all widgets on the same page count toward the same limit. Exceeding the limit returns 429 Too Many Requests. The server sends standard RateLimit-* headers on every response so you can inspect your current usage.

In practice, the limit is generous enough for any real user session. It is designed to block automated abuse rather than legitimate page loads or feedback submissions.

What is safe to make public

The data-app-key attribute (App ID) is intentionally public. It is a short opaque identifier with no elevated privilege. On its own it cannot submit feedback or read any data; it must accompany a request that passes origin validation.


Where to start

  1. Go to Create → Apps in the dashboard.
  2. Click Create App, pick a name, select your target room, choose a theme, and set your allowed origins.
  3. Copy the embed snippet from the confirmation screen and paste it before the closing </body> tag on any page listed in your allowed origins.
  4. Save your API key — it is shown only once.