Docs / Integrations
Feedback SDK
Add a feedback button to your app: bug reports with screenshots, annotations and screen recordings, feedback and feature requests — each lands as a ticket on your Planytic board.
Overview
The SDK puts a feedback panel inside your product. Your users can:
- Report a bug, with a screenshot of the page they are on (which they can annotate and redact) or a screen recording.
- Send feedback: ideas, praise, or anything on their mind.
- Request a feature.
Every report becomes a ticket on the Planytic board you choose, typed as a bug, feedback or feature request. It comes with who sent it, the page URL, browser and viewport, the last console errors, and any metadata your app adds, so the team can reproduce it without a follow-up email.
Quick start
Create a feedback app
In Planytic, open your project's Settings → Feedback SDK and click New app. Choose the default board, which report types to accept, and the sites allowed to use the key (for examplehttps://app.example.com). You get a publishable key that starts withpk_.Add the widget to your app
The fastest way is a script tag before
</body>:index.html<script src="https://api.planytic.com/sdk/feedback.js" data-key="pk_your_key" defer></script>Send a test report
A Feedback button appears in the bottom-right corner. Report a bug and it shows up on your board in seconds, with the key (e.g.BUG-42) shown to the reporter as their reference.
Install
Script tag
Works on any site. The script is served by the Planytic API and initialises itself from its data-* attributes (see Options).
<script src="https://api.planytic.com/sdk/feedback.js" data-key="pk_your_key" defer></script>npm
npm install @planytic/feedback-sdkimport { init } from "@planytic/feedback-sdk"; const planytic = init({ key: "pk_your_key" });init() must run in the browser. Calling it again replaces the previous widget, so hot reloads don't stack buttons.
React and Next.js
import { PlanyticFeedback } from "@planytic/feedback-sdk/react"; export function App({ user }) { return ( <> {/* …your app… */} <PlanyticFeedback appKey={process.env.NEXT_PUBLIC_PLANYTIC_KEY} user={user && { id: user.id, email: user.email, name: user.name }} /> </> );}The React entry is marked 'use client', so it can be rendered from a Next.js server component tree. More in React.
Identify users
Reports are anonymous by default; the form then offers an optional email field so you can follow up. When your users are signed in, identify them and the email field disappears:
// after sign-inplanytic.identify({ id: user.id, email: user.email, name: user.name }); // after sign-out: back to anonymousplanytic.identify(null);<script> // the script tag exposes window.PlanyticFeedback window.addEventListener("load", () => { PlanyticFeedback.identify({ id: "u_42", email: "ada@example.com" }); });</script>id, email and name are all optional and appear on the ticket as the reporter. Turn the anonymous email field off with askEmail: false.
Routing and labels
Each feedback app has a default board and column. In the app's settings, every report type can override them and add labels:
| Report type | Example routing |
|---|---|
bug | Engineering board → Triage column, labels bug + from-app |
feedback | Default board and column, label feedback |
feature | Roadmap board → Ideas column, label feat |
- Boards and labels must belong to the app's project.
- Labels can be created right from the routing picker. Type a name and choose Create.
- If a routed board, column or label is deleted later, reports keep working: they fall back to the default board and column, and missing labels are skipped.
- Routing is decided on the server, so changing it takes effect immediately, with no redeploy of your app.
Options
| Option | Script tag | Default | Description |
|---|---|---|---|
key | data-key | — | Publishable key (pk_…). Required. |
api | data-api | https://api.planytic.com | Planytic API base URL. |
launcher | data-launcher | true | Show the floating button. Set false to open the panel from your own UI. |
position | data-position | bottom-right | bottom-right · bottom-left · top-right · top-left |
label | data-label | Feedback | Launcher text. |
theme | data-theme | auto | auto (follows the OS) · light · dark |
accentColor | data-accent | indigo | Any CSS colour, including var(--your-token). |
appVersion | data-app-version | — | Your build or version, attached to every report. |
user | — | anonymous | { id?, email?, name? }, same as identify(). |
metadata | — | — | Key/values attached to every report (≤ 8 KB as JSON). |
askEmail | — | true | Ask anonymous users for an optional follow-up email. |
mask | — | — | Extra CSS selectors pixelated in screenshots. |
capture | — | { console: true, network: true } | Set console or network to false to never attach them from this app, even when the app allows it in Planytic. |
zIndex | — | 2147483000 | Stacking order of the widget. |
onSubmit / onError | — | — | Callbacks after a report is filed or fails. |
Your own button
const planytic = init({ key: "pk_your_key", launcher: false }); document.querySelector("#report-bug").addEventListener("click", () => { planytic.open("bug"); // straight to the bug form});Controller API
init() returns a controller. With the script tag, open, close, identify, setMetadata and submit are on window.PlanyticFeedback, and PlanyticFeedback.current() returns the full controller.
planytic.open(); // the menuplanytic.open("feature"); // or straight to one formplanytic.close();planytic.toggle(); planytic.identify({ id, email, name }); // or null for anonymousplanytic.setMetadata({ plan: "pro", tenant: "acme" }); // file a report without the UI — e.g. from an error boundaryconst { reference } = await planytic.submit({ type: "bug", title: "Crash in export", description: error.stack, screenshot: await planytic.captureScreenshot(), metadata: { route: location.pathname },}); planytic.destroy(); // remove the widget| Method | Description |
|---|---|
open(type?) | Open the menu, or one form: "bug", "feedback" or "feature". |
close() / toggle() | Close or toggle the panel. |
identify(user | null) | Attach reports to a user, or go back to anonymous. |
setMetadata(object) | Replace the metadata sent with every report. |
submit(report) | File a report without the UI. Resolves with { reference }. |
captureScreenshot() | A Blob of the visible page, with masked fields pixelated. |
destroy() | Remove the widget and its listeners. |
React
<PlanyticFeedback /> takes every option above, with appKey in place of key (which React reserves) plus disabled to render nothing. Changing user or metadata updates the widget without remounting it.
usePlanyticFeedback() gives any component below it open, close, toggle, submit and captureScreenshot:
import { usePlanyticFeedback } from "@planytic/feedback-sdk/react"; function ReportProblemItem() { const feedback = usePlanyticFeedback(); return <button onClick={() => feedback.open("bug")}>Report a problem</button>;}Privacy
- Password fields and anything marked
data-planytic-maskare pixelated in the browser, before a screenshot is uploaded. Add selectors with themaskoption. - Users see and can redact the screenshot in the annotation editor before sending it.
- Console logs and network requests are attached to bug reports only when you switch them on for the app (Project settings → Feedback SDK). They're off by default. Headers and bodies are never read. Bearer tokens, JWTs and parameters like
token,key,passwordandcodeare redacted in the browser, and the report form tells users what's included. - Requests never carry cookies (
credentials: "omit"), and the widget lives in a Shadow DOM, so it neither reads nor styles your page. - The widget marks itself
data-planytic-ignore, so it stays out of its own screenshots and is easy to exclude from your session replays or tests.
<!-- pixelated in every screenshot --><p data-planytic-mask>Card on file: 4242 4242 4242 4242</p> <script> // or by selector init({ key: "pk_your_key", mask: [".account-number", "#ssn"] });</script>Screenshots and recordings
Screenshot
The visible part of the page is rendered from the DOM, so there is no permission prompt. If a page can't be rendered that way (some cross-origin content), the widget falls back to the browser's "share this tab" prompt. Users annotate with draw, arrow, box, text and redact tools. Images over 5 MB are re-encoded as JPEG automatically.
Screen recording
Uses the browser's screen sharing (getDisplayMedia + MediaRecorder): WebM on Chrome, Edge and Firefox, MP4 on Safari. A recording stops itself before the 25 MB upload limit (about 2½ minutes), or when the user clicks the browser's "Stop sharing".
Desktop only
Phones and tablets can't share their screen from a browser, so the Record screen button is hidden there. Screenshots still work.Keys and security
- The
pk_key is publishable: it can only file reports onto its app's board and cannot read anything, so it is safe in browser code. Never put a Planytic agent token (pl_agent_…) in a client app. - Allowed origins limit which sites may use the key. Use exact origins (
https://app.example.com) or one-level wildcards (https://*.preview.example.com). An empty list allows any site, which is fine for testing but not for production. - Rate limits: each visitor gets the app's per-minute limit (10 by default), and each app is capped at 20× that across all visitors.
- Rotate a leaked key from the app's menu: the old key stops working immediately. Pause intake to stop reports without removing the widget.
REST API
The widget talks to two public endpoints. Call them directly to file reports from your own UI or backend. Authenticate with the X-Planytic-Key header (or Authorization: Bearer pk_…).
Calling from a server
Browsers send anOrigin header automatically, and the key's allowed origins are checked against it. Server-side calls must send one that is on the list, or use a separate feedback app with no origin restriction.GET /api/sdk/v1/config
The app's enabled report types and upload limits.
curl https://api.planytic.com/api/sdk/v1/config \ -H "X-Planytic-Key: pk_your_key" \ -H "Origin: https://app.example.com"{ "data": { "type": "feedback-apps", "id": "01a0…", "attributes": { "name": "Customer web app", "enabledTypes": ["bug", "feedback", "feature"], "maxScreenshotKilobytes": 5120, "maxRecordingKilobytes": 25600 } }}POST /api/sdk/v1/reports
JSON, or multipart/form-data when sending files. In multipart, reporter and context are JSON strings.
| Field | Rules |
|---|---|
type | Required. bug · feedback · feature, and enabled on the app. |
title | Required. Up to 255 characters. |
description | Optional. Up to 20,000 characters. |
reporter | Optional. { id?, email?, name? } |
context | Optional. { url, pageTitle, userAgent, language, viewport, consoleErrors (≤ 20), consoleLogs (≤ 100, if enabled), network (≤ 50, if enabled), appVersion, metadata (≤ 8 KB) } |
screenshot | Optional file. PNG, JPEG or WebP, up to 5 MB. |
recording | Optional file. WebM or MP4, up to 25 MB. |
curl https://api.planytic.com/api/sdk/v1/reports \ -H "X-Planytic-Key: pk_your_key" \ -H "Origin: https://app.example.com" \ -H "Accept: application/json" \ -F type=bug \ -F title="Export button does nothing" \ -F description="Clicked Export on /reports, nothing downloaded" \ -F 'reporter={"id":"u_42","email":"ada@example.com"}' \ -F 'context={"url":"https://app.example.com/reports","appVersion":"2.3.1"}' \ -F screenshot=@screenshot.pngHTTP/1.1 201 Created { "data": { "type": "feedback-reports", "attributes": { "reference": "BUG-42" } }}Troubleshooting
| Symptom | Cause and fix |
|---|---|
| No Feedback button | The widget only appears after the key checks out. Look in the browser console for a “[Planytic] feedback widget disabled” warning, and check the app isn't paused. |
401 feedback_key_invalid | Unknown, rotated or paused key. Copy the current key from Settings → Feedback SDK. |
403 feedback_origin_denied | This site isn't in the app's allowed origins. Add the exact origin, scheme and port included (http://localhost:5173). |
422 | A report type that isn't enabled, a missing title, or a file over its limit. The response says which field. |
429 | Rate limited. Wait a minute, or raise the per-visitor limit in the app's settings. |
| Record screen missing | The browser can't share its screen (phones, tablets, or very old browsers). |
Ready to try it?
Open a project's Settings → Feedback SDK, create an app, and paste the snippet. The install dialog fills in your key.
Open Planytic →