Feedback Widget
Embed an in-app feedback capture widget in your web application.
The ProductSights feedback widget is a lightweight, embeddable component that captures user feedback directly inside your web application. Users can submit feedback without leaving the page, and every submission flows through the same AI triage pipeline as your other sources.
What it does
- Floating button — a configurable button that opens a feedback panel
- Feedback form — captures feedback text, optional name/email fields, and shared tags
- Contextual capture — automatically includes the page URL and title where feedback was submitted
- Success confirmation — shows a confirmation message after submission
Installation
Add the widget script to your site — just before </body> or anywhere in <head> with async:
<script
src="https://productsights.io/widget.js"
data-api-key="ps_your_api_key_here"
async
></script>
The script auto-initializes when it detects a data-api-key attribute. No additional JavaScript is required.
Script-tag options
You can pass configuration via data-* attributes on the script tag:
<script
src="https://productsights.io/widget.js"
data-api-key="ps_your_api_key_here"
data-position="bottom-left"
data-button-text="Send feedback"
data-button-color="#7C3AED"
data-form-title="We'd love your feedback"
data-show-name
data-show-email
data-show-category
data-categories="Bug,Feature Request,Question"
async
></script>
Manual initialization
If you prefer programmatic control, omit data-api-key from the script tag and call init() yourself:
<script src="https://productsights.io/widget.js" async></script>
<script>
window.addEventListener('load', function () {
ProductSights.init({
apiKey: 'ps_your_api_key_here',
position: 'bottom-right',
buttonColor: '#4F46E5',
formTitle: 'Share your feedback',
showName: true,
showEmail: true,
});
});
</script>
The global ProductSights object also exposes open(), close(), setUser({ name, email }), embed(), and destroy(). Use setUser() to prefill the optional name and email fields before opening the form.
Headless mode
If you want to trigger the feedback panel from your own button instead of the built-in floating button, use headless mode. The widget initializes without any visible UI — call open() when you're ready to show the form:
<script
src="https://productsights.io/widget.js"
data-api-key="ps_your_api_key_here"
data-headless
async
></script>
<button onclick="ProductSights.open()">Give feedback</button>
Or programmatically:
ProductSights.init({
apiKey: 'ps_your_api_key_here',
headless: true,
formTitle: 'Tell us what you think',
showName: true,
showEmail: true,
});
document.getElementById('my-feedback-btn').addEventListener('click', () => {
ProductSights.open();
});
Embed mode
To render the feedback form directly inside a container on your page — no floating button, no modal overlay — use embed():
<div id="feedback-form"></div>
<script src="https://productsights.io/widget.js" async></script>
<script>
window.addEventListener('load', function () {
ProductSights.embed('#feedback-form', {
apiKey: 'ps_your_api_key_here',
formTitle: 'Share your feedback',
showName: true,
showEmail: true,
showCategory: true,
buttonColor: '#7C3AED',
onSubmit: function (response) {
console.log('Submitted:', response.id);
},
});
});
</script>
embed() accepts a CSS selector string or an HTMLElement as the first argument. The form renders inside a Shadow DOM to avoid style conflicts with your page. Set formTitle to an empty string to hide the heading.
Configuration
| Option | data-* attribute | Description | Default |
|---|---|---|---|
| apiKey | data-api-key | Your ProductSights API key | Required |
| endpoint | data-endpoint | Custom API endpoint (self-hosted) | https://productsights.io |
| headless | data-headless | Skip the floating button and open the form from your own UI | false |
| position | data-position | bottom-right, bottom-left, top-right, top-left | bottom-right |
| buttonText | data-button-text | Text on the floating button | Feedback |
| buttonColor | data-button-color | Background color of the button and form accent | #4F46E5 |
| textColor | data-text-color | Text color on the button | #FFFFFF |
| formTitle | data-form-title | Heading shown in the feedback panel | Share your feedback |
| showName | data-show-name | Show an optional name field in the form | false |
| showEmail | data-show-email | Show an email field in the form | false |
| showCategory | data-show-category | Show a category dropdown | false |
| categories | data-categories | Comma-separated list of categories | Bug,Feature Request,Question,Other |
| tags | data-tags | Comma-separated tags added to every submission | — |
| onSubmit | — | JavaScript callback after a successful submission | — |
| onError | — | JavaScript callback when a submission fails | — |
How it works
- A user clicks the floating feedback button (or your own trigger, or interacts with an embedded form)
- The feedback form is shown (as a modal panel or inline, depending on mode)
- The user enters their feedback and any optional fields you enabled, like name, email, or category
- On submission, the widget sends the data to the
/api/public/captureendpoint using your API key - The feedback enters the AI triage pipeline — categorized, scored, and structured like any other source
API key
Generate an API key from Settings → API Keys in your ProductSights dashboard. For the widget, use a publishable key (ps_pub_ prefix) — it is safe to include in client-side code because it can only submit feedback, not read your data. You can optionally add domain restrictions to limit which sites can use the key.
The widget uses the same public capture endpoint as the browser extension and custom API capture clients.
API key owners must select their role/department during onboarding before widget submissions will be accepted.
Building from source
If you're working in the monorepo:
cd packages/widget
pnpm install
pnpm build
The build output is copied to apps/web/public/widget/ and served at /widget.js via a rewrite.
Rate limiting
The widget includes built-in client-side rate limiting (5 submissions per 60 seconds) to prevent abuse. Server-side rate limits also apply to the capture endpoint.