Snippets use your anonymous clientIdSign up to keep it.

Feedback widget (@usero/sdk)

A drop-in feedback button for the web. Vanilla JS, React component, or a <script> tag. The widget renders a floating button; users click it to leave a 1 to 4 rating, an optional comment, and optional screenshots. Everything lands in your dashboard automatically.

The package is open source (github.com/usero-feedback/usero) and small: the vanilla build never imports React, so Vue, Svelte, Angular, plain HTML, and Electron apps pay zero React tax.

Install

npm
npm install @usero/sdk

React

Mount it once in your root layout (it renders a fixed-position floating button, so do not put it inside individual pages):

React
import { UseroFeedbackWidget } from '@usero/sdk/react'

export function App() {
	return (
		<>
			{/* your app */}
			<UseroFeedbackWidget clientId='client_3611132dc3e64aa3' environment={process.env.NODE_ENV} />
		</>
	)
}

Vanilla JS (Vue, Svelte, Angular, Astro, Electron, anything)

Call it once at app startup:

Vanilla
import { initUseroFeedbackWidget } from '@usero/sdk'

const widget = initUseroFeedbackWidget({
	clientId: 'client_3611132dc3e64aa3',
	environment: import.meta.env.MODE,
})

// later, if you need to remove it:
widget.destroy()

Script tag (CDN, no bundler)

Add these two tags just before </body>:

Script tag
<script src="https://unpkg.com/@usero/sdk"></script>
<script>
	Usero.initUseroFeedbackWidget({ clientId: 'client_3611132dc3e64aa3' })
</script>

unpkg and jsDelivr both serve the IIFE bundle automatically.

Warning

Omit the environment option for your default environment. Do not pass a placeholder like "no-env" or "default". The dashboard treats an absent environment as the default; a placeholder string creates a separate environment and your feedback will not appear in the default inbox.

Identify your users (optional)

The widget works fully anonymous. If you have a logged-in user, identifying them lights up "who is this person" on session replays and lets you filter feedback by email in the dashboard.

React: pass the user prop (omit or pass null for logged-out visitors). The widget re-identifies automatically when the prop changes:

React
<UseroFeedbackWidget
	clientId='client_3611132dc3e64aa3'
	user={{ id: currentUser.id, email: currentUser.email, displayName: currentUser.name }}
/>

Vanilla: pass a getUser callback, called at session start:

Vanilla
initUseroFeedbackWidget({
	clientId: 'client_3611132dc3e64aa3',
	getUser: () => (currentUser ? { id: currentUser.id, email: currentUser.email } : null),
})

What's new (changelog in the widget)

Set whatsNew: true and the widget panel shows two tabs, Feedback and What's new. The second tab lists your published changelog entries, newest first, and the floating launcher shows an unread dot when there are entries the user has not opened yet. Each entry links out to its page on your public board if you have one enabled.

<UseroFeedbackWidget clientId='client_3611132dc3e64aa3' whatsNew user={{ id: currentUser.id, email: currentUser.email }} />

Only published changelog entries appear. The feed serves entries you have published from the changelog admin on your project's board; drafts are never returned, and unpublishing an entry removes it from the feed. With zero published entries the tab has nothing to show, so publish at least one first. See the public board docs for authoring entries, including drafting them from your merged pull requests.

"Shipped for you"

For identified users, the tab can open with a pinned Shipped for you section: their original feedback quoted back, plus the title of the merged pull request that resolved it. The data comes from the chain Usero already keeps: a published changelog entry links to merged pull requests, each PR links to the feedback that prompted it, and that feedback carries the submitter's email. When the email on the feedback matches the email of the current user (case-insensitive), the section appears.

Two things have to be true for a user to see it:

  1. Their original report had an email on it. Either they ticked the share-my-email option when submitting, or your app had identified them at the time.
  2. They are identified now. The widget sends the current user's email when it loads the feed (user prop in React, getUser in vanilla). Anonymous visitors see the changelog feed and the unread dot, but never the personal section.

There is no dedupe stamp on the widget side: a shipped request keeps appearing in Shipped for you whenever the user opens the tab. The one-time requester emails sent on publish are a separate mechanism with their own dedupe; turning those off does not affect the widget tab.

Under the hood the tab reads GET /api/changelog?clientId=...&email=..., a public JSON endpoint you can also call directly if you are building your own UI.

Traits and segments

The user object also takes a traits bag of primitives, anything your app knows about the person:

React
<UseroFeedbackWidget
	clientId='client_3611132dc3e64aa3'
	user={{
		id: currentUser.id,
		email: currentUser.email,
		traits: { plan: currentUser.plan, seats: currentUser.seats, mrr: currentUser.mrr },
	}}
/>

Usero stores the latest-seen traits on the person. A numeric mrr trait is promoted to a queryable field that powers the revenue rollups on the dashboard and the revenue signal in prioritize value scores.

Traits are what segments run on. A segment is a saved filter over your identified people (conditions on MRR, email domain, or any trait, all must match), built on the dashboard's Segments page. Apply one as a lens on the dashboard or prioritize view and the feedback counts, MRR rollup, and value scores recompute to just those people. Anonymous feedback is hidden while a lens is applied, since an unidentified person cannot be matched against segment rules. Membership is evaluated live, so a changed trait moves the person to the right segments on the next page load.

Options

Option Type Default Description
clientId string required Your Usero client id. See Find your clientId.
position 'left' | 'right' 'right' Which side of the viewport the button sits on.
theme Partial<WidgetTheme> auto Override colors. By default the widget follows the OS color scheme (prefers-color-scheme), with dark as the fallback. Explicit values win; partial overrides merge on top of the detected base.
title string 'Share Feedback' Panel header.
placeholder string 'Tell us what you think... (optional)' Comment placeholder.
showEmailOption boolean true Show the "share my email" checkbox.
showScreenshotOption boolean true Show the screenshot upload button (up to 3 images, 10MB each).
whatsNew boolean false Add a "What's new" tab with your published changelog entries, an unread dot on the launcher, and a personal "Shipped for you" section for identified users. See above.
environment string undefined Tag feedback with an environment. Omit for your default environment, see the warning above.
metadata Record<string, unknown> undefined Arbitrary metadata attached to every submission.
baseUrl string 'https://usero.io' Override the API host (self-hosted Usero).
plugins UseroPlugin[] undefined Opt-in plugins, for example session replay.
getUser () => User | null undefined Vanilla only. Returns the current logged-in user (or null for anonymous). React uses the user prop instead.
onSubmit (data) => void undefined Fires after a successful submission.
onError (err: Error) => void undefined Fires on init or submission error.
onOpen / onClose () => void undefined Fire when the panel opens or closes.

Content Security Policy

If your app has a strict CSP, allow https://usero.io in connect-src (and https://unpkg.com in script-src if you use the script tag). The widget makes no other cross-origin requests.

Next

  • Session replay: record sessions and link each feedback to the moment it was submitted
  • Headless: building your own UI? Keep the widget's submission pipeline, identity, and plugins, drop ours
  • POST /api/feedback: the raw API the widget submits to