Embedding the Widget
Drop a single script tag onto any website; multi-tenant and anonymous-first.
The feedback widget is a single <script> tag you drop into any website.
It is multi-tenant and anonymous-first — it is not limited to one admin
user, and any number of unrelated sites can embed it independently.
Quick start
<script
src="https://feedback-dev.jfcreations.com/widget.js"
data-workspace="demo-workspace"
data-board="main"
></script>Drop that on any page (your site, a client's site, a CodePen — anywhere). On the first hit the workspace and board are auto-provisioned, so there's no API call or signup required to start collecting feedback.
Script tag options
| Attribute | Required | Meaning |
|---|---|---|
data-workspace | yes | Tenant slug. Pick one per site/product. Auto-created on first use. |
data-board | yes | Surface within the workspace (e.g. main, feature-requests). Auto-created. |
data-api-base | no | Override the API origin. Defaults to the script's own origin (so you usually omit it). |
Multi-tenancy — answering "is it only my single admin user?"
No. There are three independent layers:
- Workspaces = tenants. Each embedding site uses its own
data-workspace; rows are isolated byworkspace_idand never bleed across tenants. Embed the script on 50 different sites with 50 differentdata-workspacevalues and you get 50 isolated boards. - End-users = the people on the embedding site. They are
anonymous-first: the widget mints an
anon_id (with a random suffix) intolocalStorage(cv_uid) so visitors can submit and vote without an account. (You can pass a realexternalUserIdwhen posting via the API to tie feedback to your own logged-in users.) - Admins = you (the operator). Admin auth (
ADMIN_API_TOKEN/ sessions) is only for the moderation/dashboard side. It has nothing to do with who can use an embedded widget.
So: anyone on any embedding site can submit/vote anonymously; you (admin) moderate. One deployment serves unlimited tenants.
Embedding on a third-party site
It just works cross-origin — the API mirrors the request Origin and sets
permissive CORS (Access-Control-Allow-Credentials: true), because the widget
is designed to live on arbitrary domains. A partner can paste:
<script
src="https://feedback-dev.jfcreations.com/widget.js"
data-workspace="acme-corp"
data-board="product"
></script>…and acme-corp/product is provisioned for them, fully isolated from yours.
Calling the API directly (no widget)
The same public endpoints back the widget, so you can build a custom UI:
# List approved, visible feedback
curl https://feedback-dev.jfcreations.com/api/v1/acme-corp/product/feedback
# Submit
curl -X POST https://feedback-dev.jfcreations.com/api/v1/acme-corp/product/feedback \
-H 'Content-Type: application/json' \
-d '{"title":"Add SSO","description":"We need Okta","externalUserId":"user-123"}'
# Upvote
curl -X POST https://feedback-dev.jfcreations.com/api/v1/acme-corp/product/feedback/42/votes \
-H 'Content-Type: application/json' \
-d '{"externalUserId":"user-123"}'
# Comment (note: field is "content")
curl -X POST https://feedback-dev.jfcreations.com/api/v1/acme-corp/product/feedback/42/comments \
-H 'Content-Type: application/json' \
-d '{"content":"+1, blocking our rollout","externalUserId":"user-123"}'Moderation behaviour (important when opening to the public)
| Source | moderation_state | Visible immediately? |
|---|---|---|
widget | approved | yes (auto-approved) |
api / mcp / import | pending | no (is_hidden=1, awaits review) |
Widget submissions auto-approve — convenient for trusted embeds, risky for
open-internet pages (spam). Before exposing a widget to anonymous traffic at
scale, decide whether to flip widget submissions to pending and rely on the
rate-limit middleware on write paths.
Production note
Replace feedback-dev.jfcreations.com with your production worker hostname when
you cut a staging/production deploy. The data-api-base attribute lets a single
copy of the snippet target different environments without changing the bundled
script.