Add a Feedback Widget to Your Web App
Your users find bugs you never will. They’re staring at a broken layout on a device you don’t own, on a network you can’t replicate, in a state your tests never exercise. The feedback loop between “user sees bug” and “you fix it” is where products die.
Most feedback ends up in Slack. “hey the thing is broken” with no screenshot, no URL, no context. Or worse — it just doesn’t get reported at all because submitting feedback is too much work.
We built @loupeink/web-sdk to make feedback zero-friction. A floating button in the corner of your app. Click it, annotate the screenshot, type a note, done. It appears instantly in your Loupe dashboard — with the URL, viewport, browser, and annotated screenshot already attached.
Here’s how to add it.
Install
If you’re using a bundler (Vite, webpack, anything modern):
npm install @loupeink/web-sdk
Or if you want to drop it into an existing site without a build step:
<script src="https://cdn.jsdelivr.net/npm/@loupeink/web-sdk/dist/index.global.js"></script>
Initialize
One call. That’s it.
import { init } from '@loupeink/web-sdk';
init({
apiKey: 'lp_your_project_api_key',
});
Call init() once, early in your app boot — main.ts, App.tsx, wherever your entry point is. The widget mounts to document.body automatically. Shadow DOM isolation means it’ll never conflict with your styles.
CDN version works the same way:
<script src="https://cdn.jsdelivr.net/npm/@loupeink/web-sdk/dist/index.global.js"></script>
<script>
Loupe.init({ apiKey: 'lp_your_project_api_key' });
</script>
Place both tags before </body>.
Getting Your API Key
- Sign in at app.loupe.ink
- Go to Organization Settings → API Keys (top-right menu → Settings → API Keys tab)
- Select a project, optionally add a label, click Generate
- Copy the
lp_…key — it is shown only once - Pass it to
init({ apiKey: '...' })
Customize It
You can adjust position, color, and button label. All optional — defaults work fine for most apps.
init({
apiKey: 'lp_your_project_api_key',
position: 'bottom-left', // 'bottom-right' | 'bottom-left' | 'top-right' | 'top-left'
color: '#6366f1', // any CSS color
buttonLabel: 'Report a bug', // button text
});
Using It with React
Initialize once at the app root. If you’re using StrictMode (which double-invokes effects), clean up with destroy():
import { useEffect } from 'react';
import { init, destroy } from '@loupeink/web-sdk';
export function App() {
useEffect(() => {
init({ apiKey: import.meta.env.VITE_LOUPE_API_KEY });
return () => destroy();
}, []);
return <YourApp />;
}
Store your API key in .env as VITE_LOUPE_API_KEY — never commit it directly.
What the Widget Actually Does
When a user clicks the feedback button:
- Screenshot capture — uses the browser’s
getDisplayMediaAPI to grab a single frame from the current tab. The browser shows its standard screen-share picker. The bitmap is clamped to 1920 px on the longest edge and JPEG-encoded at quality 0.85 client-side, so the upload stays small and your bandwidth bill stays sane. - Annotation overlay — user can draw, circle, highlight, or blur regions on the screenshot before submitting.
- Context capture — the current URL, page title, viewport size, and user agent are attached automatically.
- Submit — everything POSTs to the Loupe Edge Function, authenticated with your API key. Feedback appears in your dashboard immediately. If the project has auto-push enabled, a GitHub Issue (or Linear issue) is created in the same call.
Every submission includes the annotated screenshot, the user’s comment, a severity level (critical / major / minor / suggestion), and the page context. No configuration needed — that’s the default behavior.
Self-Hosting or Custom Endpoints
If you’re running a self-hosted Loupe instance or want to route feedback through your own backend, pass the endpoint option:
init({
apiKey: 'lp_your_key',
endpoint: 'https://your-own-server.com/feedback',
});
Your endpoint will receive a JSON body with comment, severity, screenshot (base64 JPEG data URL, longest edge clamped to 1920 px), and a context object. The API key is sent in the X-API-Key header.
Cleanup
If you need to unmount the widget (SPA route changes, test teardown, etc.), call destroy():
import { destroy } from '@loupeink/web-sdk';
destroy(); // removes the widget from the DOM
That’s It
Two minutes from npm install to a working feedback widget in your app. Feedback lands in Loupe with screenshot, URL, and context, ready to triage. If you want it routed straight to Linear or GitHub Issues, flip the auto-push toggle in your project settings. See the GitHub bug-reporting flow for the full integration.
If you’re building something and want to close the feedback loop between your users and your team, this is the fastest way to get started.
Full SDK source is on GitHub. Issues and PRs welcome.
Try Loupe free for 14 days
Download Loupe and start giving frame-precise feedback today. No credit card required.
Download Free Trial