Add the script tag
Paste this before the closing</body> tag, on every page where you want feedback (most
teams add it everywhere):
<script src="https://tryproduck.com/sdk/v1.js" data-project="pk_live_YOUR_KEY" defer></script>
| 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. |
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. |
defer | recommended | Loads the widget without blocking your page render. |
Framework snippets
- Next.js
- React (Vite/CRA)
- Plain HTML
- Google Tag Manager
- React Native
- iOS (Swift)
Use the
Script component in your root layout so the widget loads on every route:app/layout.tsx
import Script from "next/script";
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang="en">
<body>
{children}
<Script
src="https://tryproduck.com/sdk/v1.js"
data-project="pk_live_YOUR_KEY"
strategy="afterInteractive"
/>
</body>
</html>
);
}
Add the tag to
index.html. The widget mounts itself outside your React tree, so you
don’t render it as a component:index.html
<body>
<div id="root"></div>
<script type="module" src="/src/main.tsx"></script>
<script src="https://tryproduck.com/sdk/v1.js" data-project="pk_live_YOUR_KEY" defer></script>
</body>
<script src="https://tryproduck.com/sdk/v1.js" data-project="pk_live_YOUR_KEY" defer></script>
Add a Custom HTML tag that fires on the pages you want:
<script src="https://tryproduck.com/sdk/v1.js" data-project="pk_live_YOUR_KEY" defer></script>
Mobile uses the Bare React Native: A floating launcher opens the capture flow; Wrap custom components manually with Pass the same
@produck/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:npm i @produck/react-native react-native-svg react-native-safe-area-context react-native-view-shot lucide-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):App.tsx
import { SafeAreaProvider } from "react-native-safe-area-context";
import { ProduckProvider } from "@produck/react-native";
export default function Root() {
return (
<SafeAreaProvider>
<ProduckProvider
config={{
projectKey: "pk_live_YOUR_KEY",
appId: "com.yourco.app",
}}
>
<App />
</ProduckProvider>
</SafeAreaProvider>
);
}
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). Add the Babel plugin so annotation drags snap to
the underlying component:babel.config.js
module.exports = {
presets: ["babel-preset-expo"],
plugins: ["@produck/react-native/babel"],
};
<ProduckTarget sourceId hostType> 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: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
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: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:UIKit apps — attach to your window instead: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.
https://github.com/tryproduck/produck-swift
MyApp.swift
import ProduckKit
@main
struct MyApp: App {
var body: some Scene {
WindowGroup {
ContentView()
.produckFeedback(ProduckConfig(
projectKey: "pk_live_YOUR_KEY",
appId: "com.yourco.app"
))
}
}
}
SceneDelegate.swift
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.
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, replacepk_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.
- Next.js
- React (Vite/CRA)
- Plain HTML
- React Native
- iOS (Swift)
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 <body>, after {children}:
<Script
src="https://tryproduck.com/sdk/v1.js"
data-project="pk_live_YOUR_KEY"
strategy="afterInteractive"
/>
4. If the app sets a Content-Security-Policy, extend it with
script-src https://tryproduck.com and connect-src https://api.tryproduck.com.
5. Verify: start the dev server, load any page, and confirm (a) a floating duck
button appears in the bottom-right corner, (b) the browser console has no errors
from tryproduck.com, (c) pressing Cmd+Shift+Z (Ctrl+Shift+Z on Windows/Linux)
opens the widget. Report what you observed.
Set up the Produck feedback widget in this React app.
My Produck project key: pk_live_YOUR_KEY
1. The widget is one hosted script — do NOT install anything from npm and do NOT
render it as a React component; it mounts itself outside the React tree.
2. In index.html (Vite: project root; CRA: public/index.html), add before </body>:
<script src="https://tryproduck.com/sdk/v1.js" data-project="pk_live_YOUR_KEY" defer></script>
3. If the app sets a Content-Security-Policy, extend it with
script-src https://tryproduck.com and connect-src https://api.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+Z / Ctrl+Shift+Z opens the widget. Report what
you observed.
Set up the Produck feedback widget on this website.
My Produck project key: pk_live_YOUR_KEY
1. Add this tag before the closing </body> tag of every page (or the shared
layout/template if one exists):
<script src="https://tryproduck.com/sdk/v1.js" data-project="pk_live_YOUR_KEY" defer></script>
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.
Set up Produck feedback (@produck/react-native) in this React Native app.
My Produck project key: pk_live_YOUR_KEY
1. Install:
npm i @produck/react-native react-native-svg react-native-safe-area-context react-native-view-shot lucide-react-native
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 "@produck/react-native";
<SafeAreaProvider>
<ProduckProvider config={{ projectKey: "pk_live_YOUR_KEY", appId: "<bundle id>" }}>
<App />
</ProduckProvider>
</SafeAreaProvider>
4. Add the Babel plugin for component targeting — in babel.config.js plugins:
"@produck/react-native/babel"
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.
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: "<bundle id>"
))
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: "<bundle id>"
))
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 + Z (Mac) or Ctrl + Shift + Z (Windows/Linux).
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.