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

HTMLindex.html
<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:

HTMLindex.html
<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:

HTMLindex.html
<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.

HTMLindex.html
<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:

HTMLindex.html
<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:

HTMLindex.html
<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 IDs
  • use(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().

HTMLindex.html
<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:

HTMLindex.html
<script>window.__getuserfeedback?.identify({ email: "jane@example.com" }, {externalIds: [{id: "gid://shopify/Customer/123",type: "shopify_customer_id",collection: "users",encoding: "none",},],});</script>