Skip to main content
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 Mobile SDKs (deprecated).

Add the script tag

Paste this before the closing </body> tag, on every page where you want feedback (most teams add it everywhere):
The widget initializes itself as soon as it loads — no startup call needed. Install one script per app. The key belongs to your organisation, not to a campaign, so the same script serves every campaign that runs on the app: a campaign’s triggers decide which campaign a recording is filed under, and a recording no trigger started goes to your default campaign.

Allowed domains

Each key can list the domains allowed to use it, set when you create the key or edited later in Settings › SDK. A domain admits its own host and hosts one level below it, so acme.com admits app.acme.com. Requests from any other site are refused. A key with no domains works from every site.

Framework snippets

Use the Script component in your root layout so the widget loads on every route:
app/layout.tsx

Mobile SDKs (deprecated)

The React Native SDK (@tryproduck/react-native) and the native iOS SDK (ProduckKit, produck-swift) are deprecated as of September 2026 and are no longer published or supported. Submissions from these SDKs are no longer accepted by the API. The supported path is the web widget above.

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 SDK key from Settings › SDK, and paste it into the agent. Each prompt carries everything the agent needs — file locations, exact code, and verification steps.

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.
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:
Write the chord as modifiers and a key joined by +, in any order: 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) 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.