Integrate Conclude
Add feedback collection to your app in under 5 minutes. Choose the integration that fits your stack.
React SDK
npm install, Provider pattern
Script Tag
Any website, no build step
REST API
Any language, full control
Widget Config
Theme, modes, recording
React SDK
First-class React and Next.js support via @conclude-fyi/react. Renders natively in your app — no iframe required.
Native rendering
The React SDK renders directly in your app using Shadow DOM for CSS isolation. Screenshots use html2canvas (instant, no browser prompt). Recording captures screen plus optional camera and mic, composited into a single WebM via video-stream-merger. Camera renders as a circular pip; the user opts in to camera and mic with two toggles before recording starts.
Content Security Policy
If your app has a CSP header, add https://www.conclude.fyi to your connect-src directive so the SDK can call the API. See the CSP section below for the full list of required directives.
Content-Security-Policy: connect-src 'self' https://www.conclude.fyiInstall
npm install @conclude-fyi/react
# or
pnpm add @conclude-fyi/reactAdd the Provider
Wrap your app with ConcludeProvider. In Next.js, add a "use client" providers file.
// app/providers.tsx
"use client";
import { ConcludeProvider } from "@conclude-fyi/react";
export function Providers({ children }: { children: React.ReactNode }) {
return (
<ConcludeProvider
apiKey={process.env.NEXT_PUBLIC_CONCLUDE_API_KEY!}
user={{
id: currentUser.id,
name: currentUser.name,
email: currentUser.email,
company: "Acme Inc",
plan: "pro",
}}
>
{children}
</ConcludeProvider>
);
}// app/layout.tsx
import { Providers } from "./providers";
export default function Layout({ children }: { children: React.ReactNode }) {
return (
<html lang="en">
<body>
<Providers>{children}</Providers>
</body>
</html>
);
}Add the feedback button
Drop FeedbackButton anywhere — it renders a floating button that opens the feedback panel. Theme, label, position, and enabled modes are all pulled from your dashboard config automatically.
import { FeedbackButton } from "@conclude-fyi/react";
// Uses your dashboard config (theme, modes, colors):
<FeedbackButton />
// Override specific props if needed:
<FeedbackButton position="bottom-left" label="Bug report" accentColor="#6366f1" />That's it
Your users will see a floating button. When clicked, it opens a native panel where they can:
Title, description, email
Instant page capture, no prompt
Records interactions for 60s
Enable modes from [Your Product] → Settings → Board → Widget configuration in the dashboard. Screenshots and recordings are attached to the feedback item automatically.
Other components
Inline board
import { FeedbackBoard } from "@conclude-fyi/react";
// Renders the full feedback board inline (uses iframe)
<FeedbackBoard sort="most_voted" />Custom form
import { FeedbackForm } from "@conclude-fyi/react";
// Bare form — style it however you want
<FeedbackForm
onSubmit={(item) => console.log("Submitted:", item)}
placeholder="What can we improve?"
/>Programmatic (hook)
import { useConclude } from "@conclude-fyi/react";
function MyComponent() {
const { submitFeedback } = useConclude();
async function handleClick() {
await submitFeedback({
title: "Feature request",
body: "I would love to see..."
});
}
}Script Tag
For any website — no build step, no framework required.
Add the script before </body>
<script src="https://www.conclude.fyi/conclude-widget.js"></script>
<script>
Conclude.init({
apiKey: "pk_live_YOUR_BOARD_KEY",
// Optional overrides (dashboard config is used by default):
// position: "bottom-right",
// label: "Feedback",
// accentColor: "#8083ff",
// theme: "dark",
submitter: { // optional: identify the user
externalId: "user-123",
name: "Jane Doe",
email: "jane@acme.com",
companies: [ // optional: which company they belong to
{
id: "co_acme", // your stable company ID (used for dedup)
name: "Acme Inc",
monthlySpend: 5000, // any unit you choose; we don't interpret currency
created: "2025-01-15T00:00:00Z"
}
]
}
});
</script>The widget automatically fetches its config from your dashboard (theme, colors, modes, label, position). You can override any setting in the init() call.
Identify users and companies
Pass user and company info on every submission so feedback is linked to the right account in your dashboard. Especially useful for B2B tools where you want to know which company is asking for what.
Submitter shape
submitter: {
externalId: "user-123", // required: your stable user ID
name: "Jane Doe", // optional
email: "jane@acme.com", // optional
companies: [ // optional: array — a user can belong to many
{
id: "co_acme", // required if companies passed: your stable company ID
name: "Acme Inc", // required: display name
monthlySpend: 5000, // optional: any unit (USD, EUR, credits, ...)
created: "2025-01-15T00:00:00Z" // optional: ISO timestamp
}
]
}How dedup works
We dedup companies by id within your workspace. Re-submitting feedback with the same company id updates the existing company row (e.g. when monthlySpend changes). Multiple companies per user are supported — useful for consultants, contractors, and multi-org users.
monthlySpendis opaque — we don't interpret currency or units. Pass whatever number is comparable across your customer base. We use it inside your workspace, never globally.
REST API
Full API access for custom integrations. Authenticate with your API key.
Submit feedback
curl -X POST https://www.conclude.fyi/api/v1/widget/feedback \
-H "Content-Type: application/json" \
-H "x-conclude-api-key: pk_live_YOUR_BOARD_KEY" \
-d '{
"title": "Feature request",
"body": "I would love dark mode",
"submitterExternalId": "user-123",
"submitterEmail": "jane@acme.com",
"submitterCompanies": [
{ "id": "co_acme", "name": "Acme Inc", "monthlySpend": 5000 }
]
}'The board is derived from your API key — no boardId needed in the body.
Get board with feedback
curl https://www.conclude.fyi/api/v1/widget/boards/your-board-id \
-H "x-conclude-api-key: pk_live_YOUR_BOARD_KEY"Vote on feedback
curl -X POST https://www.conclude.fyi/api/v1/widget/votes \
-H "Content-Type: application/json" \
-H "x-conclude-api-key: pk_live_..." \
-d '{
"feedbackItemId": "item-uuid",
"submitterExternalId": "user-123"
}'Authentication
API Keys
Find your keys in Settings → API Keys in the Conclude dashboard.
pk_live_Publishable key — safe for browser code. Use in the widget script, React SDK, and client-side API calls.sk_live_Secret key — server-side only. Full workspace access. Never expose in client code, HTML, or version control.User Identification
Pass user info to connect feedback to your customers. The id field is your internal user ID — we use it to track votes and prevent duplicates. Email and name are optional but recommended for notifications.
After submission — AI enrichment
You don't need to do anything for this — it happens server-side after every feedback submission. Knowing what gets extracted helps you understand what shows up in the dashboard and public board.
Type
Classified as one of feature, bug, kudos, or question. Visible as a badge on every feedback row and on the public card.
Severity
One of urgent, notable, or normal. Internal-only — surfaces in the dashboard for triage, never on the public board.
Category + area + feature
Descriptive topic labels that use your product context — e.g. Dashboard · Charts.
Sentiment + embedding
Sentiment powers the insights engine; the embedding powers clustering and duplicate detection.
Everything above is overridable in the feedback detail view if the AI gets it wrong.
Framework Guides
Step-by-step setup for popular frameworks.
Next.js (App Router)
Install the SDK
npm install @conclude-fyi/reactCreate a client component
// app/feedback-widget.tsx
"use client";
import { ConcludeProvider, FeedbackButton } from "@conclude-fyi/react";
export function FeedbackWidget() {
return (
<ConcludeProvider
apiKey={process.env.NEXT_PUBLIC_CONCLUDE_API_KEY!}
user={{
id: currentUser.id,
name: currentUser.name,
email: currentUser.email,
}}
>
<FeedbackButton />
</ConcludeProvider>
);
}Add to your layout
// app/layout.tsx
import { FeedbackWidget } from "./feedback-widget";
export default function Layout({ children }: { children: React.ReactNode }) {
return (
<html lang="en">
<body>
{children}
<FeedbackWidget />
</body>
</html>
);
}No frame-src CSP needed — the SDK renders natively, not in an iframe. If your app has a connect-src CSP directive, add https://www.conclude.fyi so the SDK can fetch your widget config and submit feedback.
React (Vite / CRA)
Install
npm install @conclude-fyi/reactAdd to your App component
// src/App.tsx
import { ConcludeProvider, FeedbackButton } from "@conclude-fyi/react";
function App() {
return (
<ConcludeProvider
apiKey={import.meta.env.VITE_CONCLUDE_API_KEY}
user={{
id: user.id,
name: user.name,
email: user.email,
}}
>
<div className="app">
{/* Your app content */}
</div>
<FeedbackButton />
</ConcludeProvider>
);
}That's it — no iframe or routing config. If your app sends a CSP header, remember to allow https://www.conclude.fyi in connect-src.
Vanilla JavaScript / Any Website
No build step, no framework. Just paste this before </body>.
<script src="https://www.conclude.fyi/conclude-widget.js"></script>
<script>
Conclude.init({
apiKey: "pk_live_YOUR_BOARD_KEY",
// All optional - uses dashboard config by default:
// position: "bottom-right",
// label: "Feedback",
// accentColor: "#8083ff",
submitter: {
externalId: "user-123",
name: "Jane Doe",
email: "jane@acme.com",
companies: [
{ id: "co_acme", name: "Acme Inc", monthlySpend: 5000 }
]
}
});
</script>The script fetches your widget config from the dashboard (theme, colors, modes, label). Override any option in the init() call. On mobile, the button shows just an icon. See Identify users and companies for the full submitter shape.
Widget Configuration
Customize the widget appearance and behavior from your board settings. The widget automatically fetches its config on load.
Available options
| Option | Type | Default | Description |
|---|---|---|---|
theme | "light" | "dark" | "auto" | "dark" | Widget color theme. "auto" matches the user's system preference. |
accentColor | string | "#8083ff" | Primary color for buttons and accents (hex). |
buttonLabel | string | "Feedback" | Text shown on the floating feedback button. |
buttonPosition | "bottom-right" | "bottom-left" | "bottom-right" | Where the floating button appears. |
modes | ("text" | "screenshot" | "recording")[] | ["text"] | Enabled feedback modes. Multiple modes show a selector before the form. |
emailField | "required" | "optional" | "hidden" | "optional" | Controls the email field visibility in the feedback form. |
logoUrl | string? | — | Custom logo URL shown in the widget header. |
Feedback modes
Text
Standard text form with title, description, email, and image attachments.
Screenshot
React SDK: Instant page capture via html2canvas — no prompt. Script tag: Browser Screen Capture API.
Recording
Screen + optional camera + optional mic, composited into one WebM. Up to 60s. Both SDK paths (React + script) use the same composite engine.
Configure via dashboard
Go to [Your Product] → Settings → Board tab to configure all options visually. Changes take effect immediately — the widget fetches its config on each load.
Configure via API
curl -X PATCH https://www.conclude.fyi/api/internal/boards/{boardId}/widget-config \
-H "Content-Type: application/json" \
-H "Cookie: __session=..." \
-d '{
"theme": "auto",
"accentColor": "#6366f1",
"modes": ["text", "screenshot", "recording"],
"emailField": "required"
}'Content Security Policy (CSP)
If your app doesn't send a CSP header, skip this section — the widget works out of the box. If you do, add the following entries so the SDK can reach Conclude.
| Directive | Value | Required by |
|---|---|---|
| connect-src | https://www.conclude.fyi | All SDKs — API requests |
| img-src | https://www.conclude.fyi blob: data: | All SDKs — screenshots & logos |
| media-src | https://www.conclude.fyi blob: | Recording mode (playback) |
| script-src | https://www.conclude.fyi | Script tag users only |
| frame-src | https://www.conclude.fyi | iframe embed users only |
React SDK
Only needs connect-src, img-src, and (if using recording mode) media-src. No frame-src or script-src required — the SDK is installed from npm and renders natively in your React tree.
Full CSP header (React SDK)
HTTP Header
Content-Security-Policy: connect-src 'self' https://www.conclude.fyi; img-src 'self' https://www.conclude.fyi blob: data:; media-src 'self' https://www.conclude.fyi blob:;Next.js (next.config.ts)
async headers() {
return [{
source: "/(.*)",
headers: [{
key: "Content-Security-Policy",
value: [
"connect-src 'self' https://www.conclude.fyi",
"img-src 'self' https://www.conclude.fyi blob: data:",
"media-src 'self' https://www.conclude.fyi blob:",
].join("; "),
}],
}];
}Meta tag
<meta http-equiv="Content-Security-Policy" content="connect-src 'self' https://www.conclude.fyi; img-src 'self' https://www.conclude.fyi blob: data:; media-src 'self' https://www.conclude.fyi blob:;">Script tag / iframe users
Also add script-src (to load conclude-widget.js) and frame-src (for the widget iframe overlay):
Content-Security-Policy:
connect-src 'self' https://www.conclude.fyi;
script-src 'self' https://www.conclude.fyi;
frame-src 'self' https://www.conclude.fyi;
img-src 'self' https://www.conclude.fyi blob: data:;
media-src 'self' https://www.conclude.fyi blob:;Symptoms if CSP is blocking the widget:
- Network tab:
net::ERR_BLOCKED_BY_CSPon fetches to www.conclude.fyi - Console:"Refused to connect to 'https://www.conclude.fyi/...' because it violates the Content Security Policy directive: "connect-src ...""
- Script tag: "Refused to frame www.conclude.fyi" → missing
frame-src - Screenshots broken: missing
img-src blob:ordata: - Recording playback broken: missing
media-src blob:
Local development
If you're testing against localhost:3000, include both:
connect-src 'self' http://localhost:3000 https://www.conclude.fyiNeed help? Reach out at support@conclude.fyi