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). Session recordings use rrweb (captures DOM interactions, ~50KB vs 10MB video). No iframe, no permissions dialogs.
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 Boards → Widget Settings 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"
}
});
</script>The widget automatically fetches its config from your dashboard (theme, colors, modes, label, position). You can override any setting in the init() call.
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"
}'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.
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"
}
});
</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.
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
React SDK only. rrweb session replay — captures DOM interactions for 60s. Lightweight JSON (~50KB), playable in dashboard.
Configure via dashboard
Go to Boards → [Your Board] → Settings → Widget 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