Open Widget flows from code

Open a Widget flow (survey, form, or message) from your app when the moment is right.

Last reviewed

Open Widget flows from code

Most flows can be sent automatically from the getuserfeedback.com dashboard. Use On-demand Widget delivery when your app should decide when to ask, like after setup, from a dedicated feedback button, or from an inline thumbs up/down control. A flow can be a survey, a form, or a question-less in-app message.

Set the flow delivery channel to Widget and the delivery method to On-demand, then use the flow ID for the flow you want to open. Find it in the getuserfeedback.com dashboard under your flow's settings.

React SDK

TypeScriptfeedback-button.tsx
import { useFlow } from "@getuserfeedback/react";import { Loader2 } from "lucide-react";function FeedbackButton() {const { isLoading, open } = useFlow({ flowId: "YOUR_FLOW_ID" });return (<buttondisabled={isLoading}type="button"onClick={() =>void open().catch((error) =>console.error("Unable to open feedback", error),)}>{isLoading ? <Loader2 /> : null}Give feedback</button>);}

Use useFlow inside any component under GetUserFeedbackProvider. Handle the returned promise when opening a flow.

React Native SDK

Use useFlow inside any component under GetUserFeedbackProvider. React Native command methods return promises and reject on failure. Await when sequencing matters; otherwise handle the rejection explicitly, typically with void operation.catch(...):

TypeScriptfeedback-button.tsx
import { Button } from "react-native";import { useFlow } from "@getuserfeedback/react-native";export function FeedbackButton() {const { open } = useFlow({ flowId: "YOUR_FLOW_ID" });return (<Buttontitle="Give feedback"onPress={() =>void open().catch((error) =>console.error("Unable to open feedback", error),)}/>);}

Use client.flow(flowId) from useGetUserFeedback() when you need the same operations outside a flow-specific hook.

JavaScript SDK

TypeScriptfeedback-button.ts
void client.flow("YOUR_FLOW_ID").open().catch((error) => console.error("Unable to open feedback", error));

See Handle command promises for the promise handling pattern.

Use the same flow handle for prefetch(), prerender(), open(), and close() when you want to keep one flow lifecycle together.

If you want to preserve lightweight response metadata, pass it on open():

TypeScriptfeedback-button.ts
await client.flow("YOUR_FLOW_ID").open({metadata: {tags: {journey_stage: "onboarding",survey_moment: "post-action",},},});

Google Tag Manager / loader

async function openFeedback() {await window.__getuserfeedback?.flow("YOUR_FLOW_ID").open();}void openFeedback().catch((error) =>console.error("Unable to open feedback", error),);

Make opening feel instant

If you know an on-demand Widget flow is likely to open, for example from a help menu item, you can prefetch and prerender it ahead of time:

React SDK

TypeScripthelp-menu-feedback-item.tsx
import { useFlow } from "@getuserfeedback/react";import { Loader2 } from "lucide-react";function HelpMenuFeedbackItem() {const { isLoading, prerender, open } = useFlow({flowId: "YOUR_FLOW_ID",prefetchOnMount: true,});const prepare = () =>void prerender().catch((error) =>console.error("Unable to prepare feedback", error),);return (<buttondisabled={isLoading}type="button"onMouseEnter={prepare}onFocus={prepare}onClick={() =>void open().catch((error) =>console.error("Unable to open feedback", error),)}>{isLoading ? <Loader2 /> : null}Share feedback</button>);}

JavaScript SDK

TypeScripthelp-menu-feedback.ts
const flow = client.flow("YOUR_FLOW_ID");async function prepareFeedback() {await flow.prefetch();await flow.prerender();}void prepareFeedback().catch((error) =>console.error("Unable to prepare feedback", error),);// Later, from the menu item:void flow.open().catch((error) =>console.error("Unable to open feedback", error),);

Await preparation steps when they depend on one another, and handle the rejection from the overall operation. See Handle command promises for the promise handling pattern.

React Native SDK

Keep one flow object when preparation and opening belong to the same flow lifecycle:

TypeScripthelp-menu-feedback-item.tsx
import { useEffect, useMemo } from "react";import { Button } from "react-native";import { useGetUserFeedback } from "@getuserfeedback/react-native";export function HelpMenuFeedbackItem() {const client = useGetUserFeedback();const flow = useMemo(() => client.flow("YOUR_FLOW_ID"), [client]);useEffect(() => {void flow.prerender().catch((error) => console.error("Unable to prepare feedback", error));}, [flow]);return (<Buttontitle="Share feedback"onPress={() =>void flow.open().catch((error) => console.error("Unable to open feedback", error))}/>);}

Prefetching loads flow resources over the network. Prerendering warms up the UI before opening. Opening still handles the render step if you have not prerendered first.

Common mistake

Don't call open() before the widget is initialized. If you set disableAutoLoad: true, make sure client.load() has run first.