JavaScript SDK reference

The full API surface of @getuserfeedback/sdk — createClient, flow handles, events, and configuration.

Last reviewed

JavaScript SDK reference

Everything revolves around one client created with createClient(). If you haven't set up yet, start with the JavaScript SDK guide.

createClient(options)

Create a single client and reuse it across your app. Calling createClient again with the same apiKey, initial flags, initial capabilities, and static action definitions returns the same instance.

TypeScriptapp.ts
import { createClient } from "@getuserfeedback/sdk";const client = createClient({ apiKey: "YOUR_API_KEY" });

If the current app version supports a newly shipped feature, report that with capabilities:

TypeScriptapp.ts
const client = createClient({apiKey: "YOUR_API_KEY",capabilities: ["checkout.drawer"],});

Options

OptionTypeDefaultDescription
apiKeystringrequiredYour project API key.
colorScheme"light" | "dark" | "system" | { autoDetectColorScheme: string[] }auto-detectColor scheme. See Dark mode.
defaultConsent"granted" | "pending" | "denied" | "revoked" | GrantScope[]"granted"Initial consent state. See Privacy & consent.
disableAutoLoadbooleanfalseWhen true, call client.load() manually before opening flows.
disableTelemetrybooleanfalseDisables anonymous performance telemetry. Does not affect user analytics.
flagsRecord<string, AppEventFlagValue> | AppEventFlag[]noneYour app's feature flag evaluations. Used by rolling theme updates.
capabilitiesArray<string | AppEventCapability>noneWhat the current app version can support. Used by capability conditions.
actionsActionRegistration[]noneStatic custom actions and an optional webpage-navigation override.

Actions (actions)

Use this when an Action button under End of flow should call code in your app. 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.

Register a custom action and load the Widget in your app. Discovery is best-effort; after the definition has been observed, 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. After the handler completes successfully, the Widget closes. The observed definition in the editor is only setup history—every live app still needs the exact registration.

const client = createClient({apiKey: "YOUR_API_KEY",actions: [{ kind: "custom", key: "open-customer-workspace",version: 1,handler: () => openCustomerWorkspace(),}],});

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.

With the default auto-load behavior, the first createClient() call starts loading. After that, adding, removing, or changing a custom key or version throws an error. Calling createClient() again with the same API key, initial flags, initial capabilities, and definitions may refresh handler functions.

version is the positive integer version of the contract between an authored action and its handler. Increase it when the meaning or expected behavior of a custom action changes incompatibly. The Widget requires an exact key-and-version match and never guesses compatibility.

Add one open-url action when your app router should get the first chance to handle authored webpage actions:

const client = createClient({apiKey: "YOUR_API_KEY",actions: [{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 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 handler must return "handled" or "unhandled" synchronously. 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.

Before display, automatic Widget flows that require a missing action are suppressed. An explicit client.flow(flowId).open() request rejects instead of rendering an incomplete flow.

Client methods

Identity

  • client.identify(userId, traits?, options?) — associate a user ID, traits, and optional external IDs with the current user
  • client.identify(traits, options?) — associate traits with the current user
  • client.identify(traits, undefined, options?) — three-argument form for call sites that keep options separate
  • client.reset() — clear identity and auth state on logout

Configuration

  • client.configure({ colorScheme?, consent?, auth?, capabilities? }) — update settings at runtime
  • client.load() — manually start the widget when disableAutoLoad is true
  • client.close() — close any open flow

Use capabilities when the supported capabilities become known or change after the widget has loaded:

client.configure({capabilities: ["checkout.drawer", "messages.compose.v2"],});

The widget checks for newly eligible flows after capabilities change. See Capabilities.

Flows

  • client.flow(flowId) — get a reusable flow handle (see below)
  • client.flow(flowId).open(options?) — open a flow
  • client.flow(flowId).prefetch() — load flow resources over the network
  • client.flow(flowId).prerender(options?) — warm up the UI before opening

Observation

  • client.track(eventName, properties?, options?) — track a product event with optional external IDs
  • client.graph.connect(relationship) — record that two product objects are connected
  • client.graph.disconnect(relationship) — record that the connection ended
  • client.subscribeFlowState(callback, options?) — watch flow state changes
  • client.onOpenRequested(callback) — observe open requests before the flow renders
  • client.setDefaultContainerPolicy(policy) — control default container behavior

Events

We recommend sending product events server-side through an integration such as Segment. If client-side tracking fits your app better, client.track(eventName, properties?, options?) records an event from the browser.

client.track("Checkout Started", {plan: "pro",source: "billing_page",});

You can call track() before or after login. When the same person is later identified, Identity resolution merges their events into a single profile. See Events for reference.

Use options.externalIds when an identify or track call carries an identifier from another system, such as a Shopify customer ID. External IDs help match profiles, but they are not traits by themselves. Do not put Segment-shaped externalIds in traits or event properties.

await client.identify("user_123", { email: "jane@example.com" }, {externalIds: [{id: "gid://shopify/Customer/123",type: "shopify_customer_id",collection: "users",encoding: "none",},],});client.track("Checkout Started", { plan: "pro" }, {externalIds: [{id: "gid://shopify/Customer/123",type: "shopify_customer_id",collection: "users",encoding: "none",},],});

Relationships

Use client.graph to record connections between users, accounts, workspaces, projects, or other product objects. Each endpoint needs a collection key and a stable ID.

const relationship = {from: { collection: "user", id: "user_123" },to: { collection: "account", id: "acct_456" },};await client.graph.connect(relationship);await client.graph.disconnect(relationship);

These methods record ordinary analytics observations. A disconnect ends the connection established by earlier observations; a later connect can establish it again. Collection keys use lowercase kebab-case, such as user or property-manager, and IDs must be nonblank. Invalid relationship properties remain ordinary analytics events but do not update the graph. See Groups for audience behavior and current limitations.

Flow handle

client.flow(flowId) returns a reusable handle for one specific flow. Use it when you want to prefetch, prerender, and open as part of one lifecycle.

TypeScriptfeedback-button.ts
const flow = client.flow("YOUR_FLOW_ID");flow.prefetch();flow.prerender();flow.open();

Fire-and-forget vs awaiting

open(), prefetch(), prerender(), and close() return promises, but you do not have to await them for the widget to behave correctly.

The widget manages its own execution queue internally, so both of these approaches work:

flow.prefetch();flow.prerender();flow.open();

and:

await flow.prefetch();await flow.prerender();await flow.open({metadata: {tags: {journey_stage: "onboarding",},},});

Use fire-and-forget when you just want the widget to do the work. Use await when your app logic needs to know that a step finished before doing something else.

Methods

  • open(options?) — open the flow (options include container, metadata, and hideCloseButton)
  • prefetch() — load resources over the network
  • prerender() — warm up the UI
  • close() — close the flow
  • setContainer(element | null) — attach or detach a custom container
  • getFlowState() — get the current state
  • subscribeFlowState(callback, options?) — watch state changes

See Open Widget flows from code for usage patterns, Response metadata for metadata examples, and Containers for custom container examples.