Actions
Register app handlers for Widget action buttons.
- Last reviewed
Actions
Use a custom action when an Action button under End of flow should call code in your app. Custom actions are available for Widget flows, not hosted pages or embeds. They currently work only for surveys with one ending; conditional endings don't support them.
Custom action contract
The actions configuration has two fields:
| Field | Meaning |
|---|---|
definitions | The actions available in this app, each with a stable key and positive-integer version. |
handler | One function that receives the exact definition selected in the flow. |
Register a custom action
Register the same definition in every app version that can display a Flow using the action.
JavaScript SDK
import { createClient } from "@getuserfeedback/sdk";const client = createClient({apiKey: "YOUR_API_KEY",actions: {definitions: [{ key: "open-customer-workspace", version: 1 }],handler: ({ key, version }) => {if (key === "open-customer-workspace" && version === 1) {return openCustomerWorkspace();}},},});React SDK
import { GetUserFeedbackProvider } from "@getuserfeedback/react";export function App() {return (<GetUserFeedbackProviderclientOptions={{apiKey: "YOUR_API_KEY",actions: {definitions: [{ key: "open-customer-workspace", version: 1 }],handler: ({ key, version }) => {if (key === "open-customer-workspace" && version === 1) {return openCustomerWorkspace();}},},}}><AppRoutes /></GetUserFeedbackProvider>);}React Native SDK
import { GetUserFeedbackProvider } from "@getuserfeedback/react-native";export function App() {return (<GetUserFeedbackProviderclientOptions={{apiKey: "YOUR_API_KEY",actions: {definitions: [{ key: "open-customer-workspace", version: 1 }],handler: ({ key, version }) => {if (key === "open-customer-workspace" && version === 1) {return openCustomerWorkspace();}},},}}><AppRoutes /></GetUserFeedbackProvider>);}React Native currently supports custom actions only. It does not support the
browser open-url override.
JavaScript snippet
Define the registry before loading the Widget script:
<script>window.__getuserfeedback_actions = [{kind: "custom",key: "open-customer-workspace",version: 1,handler: () => openCustomerWorkspace(),}];</script><scriptasyncsrc="https://cdn.getuserfeedback.com/widget/loader/v4/YOUR_API_KEY/loader.js"data-api-key="YOUR_API_KEY"></script>The snippet takes one snapshot of this runtime-wide registry before bootstrap. It cannot be configured after bootstrap or per instance.
Find an action in the editor
Discovery is best-effort. After the runtime has observed the definition, open or refresh the survey editor to find it in the Action selector. Select the matching definition, set the button label, and save the flow.
The definition observed by the editor is setup history. Every live app still needs to register the exact definition.
Match the key and version
The Widget requires an exact key-and-version match between the authored action
and the registered handler. It never guesses compatibility. Increase version
when the meaning or expected behavior of an action changes incompatibly.
In the JavaScript SDK, definitions become fixed when loading starts. In React and React Native, they remain fixed for the provider's mounted lifetime. The snippet takes its fixed snapshot before bootstrap. Matching handler functions can be refreshed where the platform supports it.
Handler outcomes
The grouped SDK handler receives the selected action's normalized key and
version. Legacy per-action handlers, including handlers in the JavaScript
snippet, receive no arguments. Core waits up to 10 seconds for the result.
Resolving during that window reports success, closes the Widget, and can emit
Flow Action Succeeded.
Throwing or rejecting reports failure. If the handler is still pending after 10 seconds, the Widget stops waiting: the response stays saved, the acknowledgment stays open, the button is disabled, and the Widget does not retry automatically.
The deadline does not cancel your handler. Its work can still finish, and a
later resolution can still emit Flow Action Succeeded, but it does not
retroactively close the timed-out Widget. The event records that the handler
settled successfully; it does not confirm that a later external effect
completed.
Missing actions
Before display, an automatic Widget flow that requires a missing or mismatched
action is suppressed. An explicit flow(flowId).open() request rejects instead
of rendering an incomplete flow.
Handle URL actions
Use the dedicated links integration when your app owns navigation:
links: {router: ({ url, target }) => {appRouter.navigate(url, target);},}The router receives authored HTTP(S) links synchronously and claims each one.
It returns undefined; a thrown error or promise-like return is terminal
failure. In browser SDKs, that failure also suppresses the Loader's browser
fallback. target is "self" for the current tab or "blank" for a new tab,
and may be omitted for older served links.
Keep links.router present for the client's loaded lifetime. In React and
React Native, its function may change while the provider remains mounted. In
JavaScript, calling createClient() again for the same client may refresh the
router function. Adding or removing the router after initialization throws.
In browser SDKs, omitting links keeps the browser's default navigation. In
React Native, authored links are unavailable until you provide a router.
The legacy open-url registration remains supported for compatibility. Do not
configure it together with links.router; the SDK rejects that ambiguous
ownership at client creation.
The legacy JavaScript and React SDK registration-array form, and the JavaScript
snippet registry, can give your app router the first chance to handle authored
webpage actions. Register one open-url action alongside custom actions:
actions: [{kind: "custom",key: "open-customer-workspace",version: 1,handler: () => openCustomerWorkspace(),},{kind: "open-url",handler: ({ url, target }) => {if (target !== "self" || !canRouteInApp(url)) return "unhandled";void navigateInApp(url);return "handled";},},]target is "self" for the current tab and "blank" for a new tab. It can
be undefined when the disposition is unavailable. Return "handled" only
after your router has synchronously accepted or committed the navigation
command. Return "unhandled" to use the browser fallback.
A thrown handler or invalid result reports failure and suppresses the fallback
to avoid an unexpected redirect. A later asynchronous router or destination
load failure does not retract Flow Action Succeeded after navigation was
accepted.