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):
- Your page's CSS on the widget mount element
- Custom theme configured per-app in the dashboard (Create → Apps → Customize colors)
- The built-in theme preset
Available tokens
| Token | Controls |
|---|---|
--af-accent | Primary accent: selected tags, submit button, focus highlights |
--af-bg | Feedback panel background |
--af-text | Panel text color |
--af-radius | Panel corner radius |
--af-trigger-bg | Floating trigger button background |
--af-trigger-radius | Trigger button corner radius |
--af-font | Font stack (defaults to a system stack) |
--af-z | Stacking 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:
| Attribute | Type | Description |
|---|---|---|
data-app-key | string | Required for auto-init. Your app's public ID. Omit it to load the script without mounting and call AnonfeedbackWidget.init() yourself. |
data-position | bottom-right | bottom-left | bottom-center | Overrides the position set in the app. |
data-trigger-label | string | Overrides the button label set in the app. |
data-theme | dark | mini | Overrides the theme set in the app. |
data-api-url | string | Advanced. 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:
| URL | Mutability | Cache | Use when |
|---|---|---|---|
/widget/v1/widget.js | Updated on every 1.x release | 1 hour | Default. You want fixes and features automatically. |
/widget/<version>/widget.js (e.g. /widget/1.1.3/widget.js) | Immutable, never changes | 1 year | You 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, seeversions.json. To always get the newest build automatically, use the/widget/v1/channel above (or@anonfeedback/widgeton 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:
- Download a pinned version:
https://cdn.anonfeedback.io/widget/<version>/widget.js - Serve it from your own origin and embed it with the same
data-*attributes. - 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:
| Identifier | Where it lives | Purpose |
|---|---|---|
App ID (data-app-key) | Embedded in your HTML | Public reference, safe to ship in page source |
| API key | Shown once at creation | Secret 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/andhttps://Example.comare 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:
| Limit | Window | Applies to |
|---|---|---|
| 120 requests | 1 minute | Each 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
- Go to Create → Apps in the dashboard.
- Click Create App, pick a name, select your target room, choose a theme, and set your allowed origins.
- Copy the embed snippet from the confirmation screen and paste it before the closing
</body>tag on any page listed in your allowed origins. - Save your API key — it is shown only once.