Headless submit
Submit feedback with your own UI via useSubmitFeedback or submitFeedback.
Headless submit
Use headless APIs when you own the form, modal, and styling. Deck only handles validation and HTTP transport to the widget ingest API.
Recommended: useSubmitFeedback
Must run under DeckProvider (usually with headless).
import {
DeckProvider,
useSubmitFeedback,
FEEDBACK_SENTIMENTS,
} from "@deck-io/feedback";
function App() {
return (
<DeckProvider
headless
publishableKey={pk}
user={{ id: user.id, email: user.email }}
>
<MyFeedbackModal />
</DeckProvider>
);
}
function MyFeedbackModal() {
const { submitFeedback, isSubmitting, lastError, reset } =
useSubmitFeedback();
const [sentiment, setSentiment] = useState<"positive" | "neutral" | "negative">(
"positive",
);
const [message, setMessage] = useState("");
async function onSubmit() {
const result = await submitFeedback({
widgetId: "wgt_your_widget",
sentiment,
message,
context: { surface: "settings" },
});
if (result.ok) {
// close modal, toast, etc.
// result.submissionId may be present
}
}
return (
<form
onSubmit={(e) => {
e.preventDefault();
void onSubmit();
}}
>
<select
value={sentiment}
onChange={(e) =>
setSentiment(e.target.value as typeof sentiment)
}
>
{FEEDBACK_SENTIMENTS.map((s) => (
<option key={s} value={s}>
{s}
</option>
))}
</select>
<textarea value={message} onChange={(e) => setMessage(e.target.value)} />
{lastError ? <p>{lastError}</p> : null}
<button type="submit" disabled={isSubmitting}>
Send
</button>
</form>
);
}Return value
| Field | Description |
|---|---|
submitFeedback(input) | Posts to Deck using provider key + user |
isSubmitting | true while a request is in flight |
lastError | Last error message, or null |
lastResult | Full last response (ok: true | ok: false) |
reset() | Clears error/result/submitting state |
user | User from DeckProvider |
apiUrl | Resolved ingest URL |
resolvedColorScheme | "light" | "dark" (theme-related) |
Submit input
{
widgetId: string;
sentiment: "positive" | "neutral" | "negative";
message?: string;
context?: Record<string, string | number | boolean | null | undefined>;
}user and meta (URL, user agent, SDK version, timestamp) are filled by the provider/client.
Low-level: submitFeedback
Works without React context when you pass key + user yourself (non-React hosts, advanced cases).
import { submitFeedback } from "@deck-io/feedback";
const result = await submitFeedback({
publishableKey: "pk_...",
user: { id: "user_1", email: "a@b.com" },
urlCapture: "path",
submission: {
widgetId: "wgt_...",
sentiment: "positive",
message: "Loved the new onboarding",
context: { surface: "onboarding" },
},
// optional metadata override
meta: { source: "client" },
});
if (result.ok) {
console.log(result.submissionId);
} else {
console.error(result.error, result.status);
}Prefer useSubmitFeedback in React apps under DeckProvider.
Server-side (no React)
For Node, Route Handlers, and Server Actions, import from @deck-io/feedback/server instead. Prefer createDeckFeedbackClient — it defaults URL capture to "none" and sets meta.source to "server".
import { createDeckFeedbackClient } from "@deck-io/feedback/server";
const deck = createDeckFeedbackClient({
publishableKey: process.env.DECK_PUBLISHABLE_KEY!,
});
await deck.submit({
user: { id: session.userId },
submission: {
widgetId: "wgt_...",
sentiment: "negative",
message: "API timeout on checkout",
},
meta: { url: request.headers.get("referer") ?? undefined },
});Full guide: Server-side submit.
Response shape
// success
{ ok: true, submissionId?: string }
// failure
{ ok: false, error: string, status?: number }