# What is Produck? Source: https://docs.tryproduck.com/index The self-evolving layer for your apps — powered by your users. Produck is the self-evolving layer for your apps, powered by your users. They speak their mind right on the page, pointing at the exact element they mean. Produck's agents triage what they say, write the fix, and ship the PR. Your product improves itself, from the people who actually use it. No calls to schedule. No session replays to re-watch. No backlog nobody triages. ## How the loop closes Your user holds the hotkey, points at the element, and says what's wrong. Produck pins their voice to the exact spot and snapshots the page. Every voice streams into one searchable dashboard, sorted and deduped. An agent reproduces the issue, writes the fix, and ships the PR. The user who spoke up hears back: fixed. ## What every capture holds Recorded and transcribed into searchable text — nuance surveys never catch. Feedback attached to the specific button, field, or section they meant. Freehand annotations and labels, right on top of the live page. Exactly what the user was seeing when they spoke up. ## Drop a duck into your app One script tag. Your users give voice feedback in context — nothing for them to install, nothing for you to maintain. Create a project, grab your key, and embed the widget. # Quickstart Source: https://docs.tryproduck.com/quickstart Add the widget, wire it to your repo, and let agents ship the fixes. ## Get feedback flowing In the [Produck dashboard](https://tryproduck.com/app), open **Projects** and click **Create**. Create one project per app you want feedback on. Projects — plus the GitHub connection and coding agents below — need the **Startup tier or higher**. It's free to start: sign up for the Startup tier, or upgrade from a personal account. Open your project's **settings** and click **Generate API key**. Your key starts with `pk_live_`. The full key is shown **once**, right after you generate it. Copy it before closing the dialog. Lost it? Generate a new one and delete the old. Copy the script tag from the dialog — your key is already filled in — and paste it before the closing `` tag of your app: ```html theme={null} ``` That's the whole install. Reload your app and the duck button appears in the bottom-right corner. Click the duck (or press **⌘ + Shift + ?** / **Ctrl + Shift + ?**) to open the widget. Prefer to delegate? Hand your coding agent a ready-made prompt from [Set up with an AI agent](/sdk/installation#set-up-with-an-ai-agent) — it covers web, React Native, and native iOS. Submit a test note from your app, then open the **Feedback** view in the dashboard. Your submission appears with its annotations, voice transcript, and page snapshot. ## Close the loop with coding agents Connect your repo so Produck's agents can turn a piece of feedback into a pull request. In the [dashboard](https://tryproduck.com/app), open the project you created above — or create one first — and go to **project settings**. In the **Repository** card, click **Install GitHub App** and pick the repos Produck can see. Access is **read-only** — uninstall anytime. Back in project settings, choose the **Repository** and set the **Branch** the agent works from. Agents open a pull request against this branch and never push to it directly. Open any feedback ticket. Once its project has a connected repo and a completed design doc, a **Launch** button appears on the ticket. The agent clones the repo, writes the fix, and opens a PR for your review — usually in 5–20 minutes. ## Next steps Framework snippets and where to place the script tag. Identify users, open the widget from your own UI, and listen for events. # Feedback board Source: https://docs.tryproduck.com/sdk/feedback-board Embed a public board of your product's feedback on any site with one script tag. A feedback board is a public, embeddable snapshot of your feedback log. From the dashboard's Feedback page, filter to a domain (plus any search, project, key, or date filters), then click **Export board**. Every matching entry is frozen into a published board, and the dialog hands you one script tag. Boards are available on business accounts. ## Add the script tag Paste the tag from the export dialog wherever the board should appear: ```html theme={null} ``` The board renders in place of the script tag by default. To control placement, add an empty container anywhere on the page: ```html theme={null}
``` | Attribute | Required | Description | | ------------- | ----------- | ------------------------------------------------------------------------------------------------------------------------------------ | | `src` | yes | The hosted embed: `https://tryproduck.com/sdk/board.js`. Load it from this URL so you always get updates. | | `data-board` | yes | The board id from the export dialog. | | `data-target` | optional | A CSS selector for the container to render into. Falls back to `[data-produck-board]`, then to rendering in place of the script tag. | | `defer` | recommended | Loads the embed without blocking your page render. | No API key is needed — the board id is the only credential, and only published boards are served. ## What appears on a board Each entry shows its title, description (or the first written annotation, capped at 280 characters), the page path it was left on, and the date. Submitter names and emails, audio, screenshots, and session replays never appear. Spam never appears. ## Snapshot semantics A board is frozen at export time: feedback that arrives later — even matching the same filters — does not appear on an existing board. Click **Export board** again for a fresh snapshot (each export mints a new board id and tag). If a domain claim is revoked, that domain's entries drop off every published board immediately. # Installation Source: https://docs.tryproduck.com/sdk/installation Embed the Produck widget with a single script tag. The widget is one hosted script. Drop it into your app and it loads, renders the duck button, and handles the rest — nothing to bundle, install from npm, or add to a build step. Building a mobile app? See the **React Native** and **iOS (Swift)** tabs under [Framework snippets](#framework-snippets). ## Add the script tag Paste this before the closing `` tag, on every page where you want feedback (most teams add it everywhere): ```html theme={null} ``` The widget initializes itself as soon as it loads — no startup call needed. | Attribute | Required | Description | | ------------------- | ----------- | ----------------------------------------------------------------------------------------------------------------------------------------- | | `src` | yes | The hosted widget: `https://tryproduck.com/sdk/v1.js`. Load it from this URL so you always get updates. | | `data-project` | yes | Your project's API key from the dashboard. Starts with `pk_live_`. | | `data-launcher` | optional | `droplet` (default) or `none` to suppress the built-in button. See [Bring your own UI](/sdk/javascript-api#bring-your-own-ui). | | `data-confirmation` | optional | `toast` (default) or `none` to suppress the built-in confirmation. | | `data-draggable` | optional | `on` to let users drag the launcher anywhere on screen. Off by default. | | `data-shortcut` | optional | The keyboard chord that opens the widget. Defaults to `mod+shift+?`. Set `off` to unbind it. See [Keyboard shortcut](#keyboard-shortcut). | | `defer` | recommended | Loads the widget without blocking your page render. | ## Framework snippets Use the `Script` component in your root layout so the widget loads on every route: ```tsx app/layout.tsx theme={null} import Script from "next/script"; export default function RootLayout({ children }: { children: React.ReactNode }) { return ( {children} ``` ```html theme={null} ``` Add a **Custom HTML** tag that fires on the pages you want: ```html theme={null} ``` Mobile uses the `@tryproduck/react-native` package instead of the script tag: users capture the current screen, annotate it, and submit — with optional frame replay and component-level targeting. **Install.** One command for every React Native app — bare, Expo, dev client, or Expo Go: ```sh theme={null} npm i @tryproduck/react-native react-native-svg react-native-safe-area-context react-native-view-shot@^4 ``` Bare React Native: `cd ios && pod install`. Expo: restart the dev server (or use `npx expo install` for the three native modules if you prefer SDK-pinned versions). **Wrap your app** with the provider (inside `SafeAreaProvider`): ```tsx App.tsx theme={null} import { SafeAreaProvider } from "react-native-safe-area-context"; import { ProduckProvider } from "@tryproduck/react-native"; export default function Root() { return ( ); } ``` A floating launcher opens the capture flow; `useProduckFeedback().open()` triggers it from your own UI. `appId` is your bundle id — it groups feedback the way a domain does on web. A rolling 30-second frame replay is attached to every submission by default (`replay: false` opts out). Other optional config: `screen`, `user {id, email, name}`, `launcher`, `onError`, `analyticsOptOut`, `buildId`. **Component targeting (recommended default).** The Babel plugin is the standard setup: it auto-wraps your host elements so every annotation resolves to the component it lands on. Without it feedback still works, but annotations are coordinate-only and bind to no component. Run `npx produck-rn init` to add it automatically, or edit `babel.config.js` yourself: ```js babel.config.js theme={null} module.exports = { presets: ["babel-preset-expo"], plugins: ["@tryproduck/react-native/babel"], }; ``` Wrap custom components manually with `` where needed. **Exact-source binding (optional, CI).** Upload a build manifest per CI build and Produck resolves annotated components to their exact source for that build: ```sh theme={null} produck-rn manifest --root . --build-id "$BUILD_ID" --app-id com.yourco.app \ --revision "$GIT_SHA" --output .produck/manifest.json PRODUCK_PROJECT_KEY=pk_live_YOUR_KEY produck-rn upload-manifest .produck/manifest.json ``` Pass the same `buildId` in the provider config. No native permissions are required — screenshots and replay frames render from your app's own view hierarchy. Native iOS apps use the **ProduckKit** Swift package — the same capture flow as React Native: users screenshot the current screen, annotate it with highlight, marker, draw, and eraser tools, and submit. Pure Swift, iOS 16+, no CocoaPods, no other dependencies. **Install.** In Xcode, open **File → Add Package Dependencies** and paste the ProduckKit repository URL: ``` https://github.com/tryproduck/produck-swift ``` The package resolves to a prebuilt, checksum-verified XCFramework, so it adds nothing to your compile times, and every release ships its dSYMs so your crash reports keep symbolicating. **SwiftUI apps** — one view modifier on your root view: ```swift MyApp.swift theme={null} import ProduckKit @main struct MyApp: App { var body: some Scene { WindowGroup { ContentView() .produckFeedback(ProduckConfig( projectKey: "pk_live_YOUR_KEY", appId: "com.yourco.app" )) } } } ``` **UIKit apps** — attach to your window instead: ```swift SceneDelegate.swift theme={null} import ProduckKit let handle = Produck.attach(to: window, config: ProduckConfig( projectKey: "pk_live_YOUR_KEY", appId: "com.yourco.app" )) // handle.open() triggers the flow from your own UI; // handle.noteScreen("Checkout") labels replay segments. ``` A floating launcher opens the capture flow; it lives in its own overlay window, so it stays visible across navigation pushes, tab switches, and any sheets or full-screen covers your app presents. `appId` is your bundle id — it groups feedback the way a domain does on web. A rolling 30-second frame replay is attached to every submission by default (`replay: false` opts out); if you enable replay, disclose screen capture in your app's privacy policy. Feedback posts to `https://tryproduck.com/api` by default; set `apiBase` only to point at a self-hosted or staging backend. Other optional config: `screen`, `user: SdkUser(id:email:name:)`, `launcher: false` for programmatic-only, `onError`, `analyticsOptOut`. No native permissions are required — screenshots and replay frames render from your app's own window, on-device, and upload only when the user taps Send. ## Set up with an AI agent Using Cursor, Claude Code, Copilot, or another coding agent? Copy the prompt for your framework, replace `pk_live_YOUR_KEY` with your project's API key, and paste it into the agent. Each prompt carries everything the agent needs — file locations, exact code, and verification steps. ```text theme={null} Set up the Produck feedback widget in this Next.js app. My Produck project key: pk_live_YOUR_KEY 1. The widget is one hosted script — do NOT install anything from npm. 2. Find the root layout. App Router: app/layout.tsx. Pages Router: pages/_app.tsx. 3. Add the script so it loads on every route, using next/script: import Script from "next/script"; // inside , after {children}: 3. If the app sets a Content-Security-Policy, extend it with script-src https://tryproduck.com and connect-src https://tryproduck.com. 4. Verify: start the dev server, load the app, and confirm (a) a floating duck button appears in the bottom-right corner, (b) no console errors from tryproduck.com, (c) Cmd+Shift+? / Ctrl+Shift+? opens the widget. Report what you observed. ``` ```text theme={null} Set up the Produck feedback widget on this website. My Produck project key: pk_live_YOUR_KEY 1. Add this tag before the closing tag of every page (or the shared layout/template if one exists): 2. Verify: open a page in a browser and confirm (a) a floating duck button appears in the bottom-right corner, (b) no console errors from tryproduck.com. Report what you observed. ``` ```text theme={null} Set up Produck feedback (@tryproduck/react-native) in this React Native app. My Produck project key: pk_live_YOUR_KEY 1. Install: npm i @tryproduck/react-native react-native-svg react-native-safe-area-context react-native-view-shot@^4 Bare React Native: cd ios && pod install. Expo: restart the dev server. 2. Read the app's bundle identifier (app.json "ios.bundleIdentifier" / "android.package", or the native project) — that is the appId below. 3. Wrap the root component with the provider, inside SafeAreaProvider: import { SafeAreaProvider } from "react-native-safe-area-context"; import { ProduckProvider } from "@tryproduck/react-native"; " }}> 4. Add the Babel plugin for component targeting (so every annotation resolves to the component it lands on; without it annotations are coordinate-only): run `npx produck-rn init`, or add "@tryproduck/react-native/babel" to babel.config.js plugins. 5. Notes: a rolling 30s frame replay is attached by default (replay: false opts out); no native permissions are needed. 6. Verify: typecheck passes, the app launches with a floating feedback button in the bottom-right corner, and tapping it opens the screenshot + annotation flow. Report what you observed. ``` ```text theme={null} Set up ProduckKit (Produck's native iOS feedback SDK) in this iOS app. My Produck project key: pk_live_YOUR_KEY Do all of this on a new git branch (e.g. add-produck-feedback) and open a pull request when done — do not commit to the default branch. 1. Add the Swift package https://github.com/tryproduck/produck-swift (from: "0.1.0"). Use whatever this project already uses for dependencies: Xcode project → add it to the project's Package Dependencies; Package.swift → add to dependencies + target dependencies ("ProduckKit"); XcodeGen/Tuist → the manifest. iOS deployment target must be 16.0+. 2. Read the app's bundle identifier — that is the appId below. 3. SwiftUI app (has an App struct): add the modifier to the root view: import ProduckKit ContentView() .produckFeedback(ProduckConfig( projectKey: "pk_live_YOUR_KEY", appId: "" )) UIKit-only app (no SwiftUI root): in scene/window setup, keep the handle: let produck = Produck.attach(to: window, config: ProduckConfig( projectKey: "pk_live_YOUR_KEY", appId: "" )) 4. Notes: the package resolves to a prebuilt XCFramework (no SDK source appears in the project — that is expected), no other dependencies, no CocoaPods, no Info.plist or permission changes. A rolling 30s frame replay is attached by default (replay: false opts out) — if kept on, remind me to disclose screen capture in the privacy policy. 5. Verify: the app builds, launches with a floating feedback button in the bottom-right corner, and tapping it captures the screen and opens the annotation toolbar (highlight, marker, draw). 6. Open the pull request and reply with the PR link plus what you observed during verification. ``` ## Opening the widget Once the script is on the page, your users get two ways in: * **The duck button** — floating in the bottom-right corner. * **Keyboard shortcut** — **⌘ + Shift + ?** (Mac) or **Ctrl + Shift + ?** (Windows/Linux). Want to open it from your own button instead? See the [JavaScript API](/sdk/javascript-api). The widget renders inside an isolated shadow root, so its styles never leak into your app and your CSS never touches the widget. No class-name collisions to worry about. ### Keyboard shortcut The default chord is `mod+shift+?` — `mod` is ⌘ on macOS and Ctrl everywhere else, so one value covers both platforms. If it collides with a shortcut your app already owns, rebind it with `data-shortcut`: ```html theme={null} ``` Write the chord as modifiers and a key joined by `+`, in any order: | Token | Matches | | ------------------------------------------------- | --------------------------------------------------------------------------------------- | | `mod` | ⌘ on macOS, Ctrl on Windows/Linux — the portable choice. | | `ctrl`, `cmd` (`meta`), `alt` (`option`), `shift` | That one physical modifier, on every platform. | | the final token | The key itself — a letter (`k`), a digit (`1`), or a printed character (`?`, `/`, `+`). | The key is matched against `KeyboardEvent.key` as your users actually type it, so a shifted character is written the way it prints: `mod+shift+?`, not `mod+shift+/`. Modifiers you leave out must be *absent* for the chord to fire, which keeps `mod+shift+?` from also firing on `mod+alt+shift+?`. Pass `data-shortcut="off"` to unbind the keyboard entirely and leave the duck button (or your own [custom launcher](/sdk/javascript-api#bring-your-own-ui)) as the only way in. Browsers reserve some chords for themselves and won't hand them to the page — ⌘/Ctrl + T, W, N and friends. Pick something your users' browser doesn't already claim, and verify the binding on both macOS and Windows before you ship it. # JavaScript API Source: https://docs.tryproduck.com/sdk/javascript-api Identify users, open the widget from your own UI, and react to submissions. Once the script loads, the widget exposes one global function: ```js theme={null} window.produck(command, ...args) ``` Everything here is optional — the widget works fully with just the script tag. | Command | Arguments | What it does | | ---------- | ------------------------------------------------- | ------------------------------------------------------------------------------------ | | `identify` | `{ userId?, email?, name? }` · `{ jwt }` · `null` | Attach the current user's identity to their feedback (or `null` to clear on logout). | | `trigger` | — | Open the widget from your own UI. | | `config` | `{ launcher?, confirmation? }` | Choose who owns the injected launcher and confirmation UI. | | `on` | `eventName, callback` | Listen for `submit`, `submit_error`, or `close`. | | `destroy` | — | Remove the widget and its listeners from the page. | ## Identify your users Tell Produck who's giving feedback so each submission ties back to a real user instead of being anonymous. Call `identify` once you know who they are — typically right after login. ```js theme={null} window.produck("identify", { userId: "user_123", email: "ada@example.com", name: "Ada Lovelace", }); ``` All three fields are optional — send whatever you have. ### Pass a login token (JWT) Already holding the user's session token? Hand it over and we read the standard `sub` / `email` / `name` claims for you — one line for Supabase, Auth0, Clerk, Firebase, or Cognito: ```js theme={null} window.produck("identify", { jwt: session.access_token }); ``` We only read the claims (the signature is not verified), so this labels feedback inside your own project — it is not an authentication check. ### Server-rendered pages (no JavaScript) If your backend already knows the user at render time, set `data-user-*` on the script tag and skip `identify` entirely. `data-user-jwt` works the same way if you'd rather pass a token: ```html theme={null} ``` ### Clear identity on logout ```js theme={null} window.produck("identify", null); ``` If you need to identify a user before the script finishes loading, queue the call first. The widget replays anything queued the moment it boots, so nothing is lost: ```html theme={null} ``` ## Open the widget from your own UI Wire up your own "Give feedback" button with `trigger`: ```js theme={null} document.querySelector("#feedback-button").addEventListener("click", () => { window.produck("trigger"); }); ``` ## Listen for events React when a user submits or dismisses the widget — for example, to show a thank-you toast. ```js theme={null} window.produck("on", "submit", () => { showToast("Thanks for the feedback!"); }); window.produck("on", "close", () => { // the widget surface closed }); ``` | Event | Fires when | | -------------- | ------------------------------------------------------------------------------------------------------------------------------ | | `submit` | Feedback is successfully submitted. May fire after `close` when a failed submission is retried. | | `submit_error` | A submission fails. Payload: `{ error, retry }` — call `retry()` to resubmit the same feedback (re-fires `submit` on success). | | `close` | The widget surface closes — after a submit, a cancel, or a failed submit. | ## Bring your own UI The widget injects two surfaces you can take over. Each is an ownership setting, not a boolean, so it stays meaningful as more options arrive: | Surface | Values | Default | | -------------- | ------------------ | --------------------------------------------- | | `launcher` | `droplet` · `none` | `droplet` (the floating duck button) | | `confirmation` | `toast` · `none` | `toast` (the post-submit success/error toast) | Set `none` on a surface to suppress the built-in version and render your own. Turning a surface off never removes capability — `trigger`, `submit`, and `submit_error` keep working. ```js theme={null} produck("config", { launcher: "none", confirmation: "none" }); // your launcher document.querySelector("#feedback-button").addEventListener("click", () => produck("trigger")); // your confirmation produck("on", "submit", () => myToast.success("Thanks!")); produck("on", "submit_error", ({ error, retry }) => { myToast.error("Couldn't send — retry?", () => retry()); }); ``` Set the same thing with no JavaScript using `data-*` attributes on the script tag: ```html theme={null} ``` To call `config` before the script finishes loading, queue it with the same stub shown above for `identify` — the widget replays it on boot. The keyboard shortcut (⌘/Ctrl + Shift + ?) still opens the widget when `launcher` is `none` — it's an invisible trigger, not a visible surface. Rebind it with `data-shortcut`, or unbind it with `data-shortcut="off"`, so your launcher is genuinely the only way in — see [Keyboard shortcut](/sdk/installation#keyboard-shortcut). ## Clean up on logout Call `destroy` to remove the widget when a user's session ends. On shared devices, this keeps the next user from seeing the previous one's identity. ```js theme={null} window.produck("destroy"); ```