Skip to main content
From inside a Fanvue iframe, the bridge lets your on-platform app send analytics events through Fanvue’s pipeline and ask the creator a question in a dialog that Fanvue draws. It’s a MessagePort channel between your iframe and the Fanvue page hosting it, and it exposes only the capabilities Fanvue has granted your app. React apps use the hooks in @fanvue/builder-sdk/react; other surfaces call connectFanvueBridge from @fanvue/builder-sdk/bridge. You need a surface running inside Fanvue, set up as in Run your app inside Fanvue.

Capabilities and grants

Fanvue grants capabilities per app; contact Fanvue to request them. Until a capability is granted:
  • isEnabled and isDialogAvailable stay false
  • track does nothing
  • openDialog resolves { status: "unavailable" }
That means your code can call them unconditionally and fall back to its own UI. The iframe sandbox blocks window.alert, window.confirm and window.prompt, so dialog is how you ask the creator a blocking question in Fanvue’s own chrome.

Send analytics events

useFanvueAnalytics() returns { track, isEnabled }. track(eventName, properties?, options?) is fire-and-forget, so it never throws and never rejects. In development, a payload the host would reject logs a warning naming the event. In production it’s dropped silently.
The host prefixes every name with embedded_app_, so the example lands as embedded_app_checkout_opened. That prefix means an app can never emit a core platform event. The SDK validates the payload before sending, and the host validates it again. A local failure returns invalid_payload.

Open a dialog

useDialog(options?) returns { openDialog, isDialogAvailable }. openDialog(payload) resolves a DialogOutcome and never rejects. Callbacks in options fire only while the component is mounted.
A confirm dialog asks a yes or no question. A form dialog collects 1 to 10 fields and returns their values. Every field carries key (/^[a-z0-9_]{1,64}$/), label (1 to 80 characters), optional required and optional helperText (200 characters). The host renders all copy as plain text. One dialog is open at a time. A second openDialog while one is pending is refused with busy.

Errors

BridgeRequestError carries a code and a message. The host returns five codes, and the SDK adds two of its own.

Use the bridge without React

connectFanvueBridge({ timeoutMs? }) resolves a Result<FanvueBridge, BridgeConnectError>. A connected bridge exposes capabilities, has(capability), analytics.track and dialog.open, each returning a Result. Calling a capability without checking has is safe and returns capability_denied.
Call connectFanvueBridge again after the iframe reloads. The host closes the previous port and completes a new handshake.

Handshake

The SDK runs this handshake for you, and the details matter when a connection fails. Your frame posts fanvue:bridge:ready to window.parent with { v: 1, caps }, where caps names the capabilities this SDK build understands. The host verifies the message came from the mounted iframe at your registered surface URL’s origin, then answers fanvue:bridge:hello with { capabilities } and transfers a MessagePort. All later traffic runs over the port. BridgeConnectError has two codes:
  • NOT_EMBEDDED means the page is not in an iframe.
  • TIMEOUT means no hello arrived. Either something other than Fanvue is framing the page, or the host refused the handshake because the frame’s origin doesn’t match the registered surface URL.
The host also refuses a frame on an opaque origin, such as a sandbox without allow-same-origin. The React hooks share one connection per page. A TIMEOUT is retried on the next call, while a connected bridge and NOT_EMBEDDED are cached until reload.