JavaScript snippet
The lowest-level way to load the widget — a script tag with no SDK or GTM.
- Last reviewed
JavaScript snippet
This is the lowest-level way to load the widget. A single script tag, no package manager, no GTM, no SDK — just a URL and a browser global.
Most teams are better served by the Google Tag Manager, React SDK, or JavaScript SDK. Use the snippet when none of those fit your setup.
Add the script
<scriptasyncsrc="https://cdn.getuserfeedback.com/widget/loader/v2/YOUR_API_KEY/loader.js"data-api-key="YOUR_API_KEY"></script>If a flow should only display after the current app version supports a feature, send capabilities on the script tag:
<scriptasyncsrc="https://cdn.getuserfeedback.com/widget/loader/v2/YOUR_API_KEY/loader.js"data-api-key="YOUR_API_KEY"data-capabilities="checkout.drawer,messages.compose"></script>You can also pass JSON when you need extra metadata:
<scriptasyncsrc="https://cdn.getuserfeedback.com/widget/loader/v2/YOUR_API_KEY/loader.js"data-api-key="YOUR_API_KEY"data-capabilities='[{"key":"checkout.drawer","source":"host-app"}]'></script>See Capabilities.
Register actions
Use this when an Action button under End of flow should call code on a page that loads the Widget snippet. Action buttons are available for Widget flows, not hosted pages or embeds. They currently work only for surveys with one ending and are unavailable with conditional endings.
Assign window.__getuserfeedback_actions before the Loader script, then
load the Widget on your page. Discovery is best-effort; after the definition has
been observed, open or refresh the survey editor to find it in the Action
selector. Set the button label and save the flow. After the handler completes
successfully, the Widget closes. The observed definition in the editor is only
setup history—every live page still needs the exact registration.
<script>window.__getuserfeedback_actions = [{kind: "custom",key: "open-customer-workspace",version: 1,handler: () => openCustomerWorkspace(),}];</script><scriptasyncsrc="https://cdn.getuserfeedback.com/widget/loader/v2/YOUR_API_KEY/loader.js"data-api-key="YOUR_API_KEY"></script>Replace openCustomerWorkspace() with your app's function. The handler receives
no arguments. Return or resolve within 10 seconds to report success. Throwing,
rejecting, or taking longer reports failure. The response stays saved, the
acknowledgment stays open, the button is disabled, and the Widget does not retry
automatically.
The Loader takes one pre-bootstrap snapshot of this runtime-wide registry. It
cannot be configured after bootstrap or per instance, so keep its definitions
fixed. Before display, automatic Widget flows that require a missing action are
suppressed. An explicit global flow(flowId).open() request rejects
instead of rendering an incomplete flow.
version is the positive integer version of a custom action's handler contract. Change
it when that action's meaning changes incompatibly; an authored flow requires
an exact key-and-version match.
The same registry can override webpage actions. Return "unhandled" to use the
Loader's browser navigation fallback, or "handled" when your app owns the
navigation:
<script>window.__getuserfeedback_actions = [{kind: "open-url",handler: ({ url, target }) => {if (target !== "self" || !canRouteInApp(url)) return "unhandled";void navigateInApp(url);return "handled";},}];</script>target is "self" for the current tab and "blank" for a new tab. It is
undefined when the disposition is unavailable, such as for a targetless link or
during a runtime rollout; return "unhandled" to preserve the existing browser
behavior. The result must be synchronous.
Return "handled" only after your handler has synchronously accepted or
committed the navigation command. That is terminal navigation success for the
Flow Action Succeeded event; a later asynchronous router or destination
load failure does not retract it. If your handler did not accept or commit the
command, return "unhandled" so the Loader can use its browser fallback. A
thrown handler or invalid result is treated as failure and suppresses fallback
so it cannot cause an unexpected redirect.
Browser global
Once loaded, the widget exposes window.__getuserfeedback with the same
capabilities as the SDK:
Main methods
flow(flowId).open()flow(flowId).prefetch()flow(flowId).prerender()flow(flowId).close()identify(userId, traits?, options?)identify(traits, options?)identify(traits, undefined, options?)track(eventName, properties?, options?)configure(updates)reset()
configure(updates) accepts the same runtime updates as the SDK, including
capabilities:
<script>window.__getuserfeedback?.configure({capabilities: ["checkout.drawer", "messages.compose.v2"],});</script>Multi-instance helpers
If you run more than one instance on the same page:
instances()— list active instance IDsuse(instanceId)— target a specific instance
Open-request hook
onOpenRequested(callback) lets you observe open requests before the flow
renders. Use it when your app needs to coordinate the opening behavior with its
own UI.
Track events
We recommend sending product events server-side through an integration such as
Segment. If client-side tracking fits your app better, use
track().
<script>window.__getuserfeedback?.track("Checkout Started", {plan: "pro",source: "billing_page",});</script>Events sent before and after login can resolve into the same profile through Identity resolution. See Events for reference.
Use options.externalIds when an identify or track call carries an identifier
from another system. The traits-only form can take options as the second
argument. Do not put Segment-shaped externalIds in traits or event
properties:
<script>window.__getuserfeedback?.identify({ email: "jane@example.com" }, {externalIds: [{id: "gid://shopify/Customer/123",type: "shopify_customer_id",collection: "users",encoding: "none",},],});</script>