Skip to main content

AppBridge

AppBridge is an interface that connects an App (running inside Dashboard) with the Dashboard itself.

Setup​

Create instance of AppBridge by running the following code:

import { AppBridge } from "@saleor/app-sdk/app-bridge";

const appBridge = new AppBridge(options);

Options object is the following:

type AppBridgeOptions = {
targetDomain?: string;
saleorApiUrl?: string;
initialLocale?: LocaleCode;
autoNotifyReady?: boolean;
initialTheme?: "dark" | "light";
};

AppState​

You can get the current state of the app by calling appBridge.getState():

const { token, saleorApiUrl, ready, id } = appBridge.getState();

Available state represents AppBridgeState:

type AppBridgeState = {
/**
* JWT token provided by Dashboard. Represents user's session.
*/
token?: string;
/**
* ID of the app
*/
id: string;
/**
* Flag if app bridge has properly initialized and authorized
*/
ready: boolean;
/**
* Current path on the frontend
*/
path: string;
theme: ThemeType;
locale: LocaleCode; // See src/locales.ts
/**
* Full URL including protocol and path where GraphQL API is available
**/
saleorApiUrl: string;
/**
* Versions of Saleor that app is being installed. Available from 3.15.
*/
saleorVersion?: string;
dashboardVersion?: string;
user?: {
/**
* Original permissions of the user that is using the app.
* *Not* the same permissions as the app itself.
*
* Can be used by app to check if user is authorized to perform
* domain specific actions
*/
permissions: Permission[];
email: string;
};
/**
* Permissions of the app itself
*/
appPermissions?: AppPermission[];
};

AppBridgeProvider​

AppBridgeProvider and useAppBridge hook are exposed from @saleor/app-sdk:

// app.tsx
import { AppBridgeProvider } from "@saleor/app-sdk/app-bridge";

<AppBridgeProvider>
<YourApp />
</AppBridgeProvider>;

AppBridgeProvider can optionally receive AppBridge instance in props, otherwise it will create one automatically.

useAppBridge hook​

In components wrapped with AppBridgeProvider, you can use the useAppBridge hook:

import { useAppBridge } from "@saleor/app-sdk/app-bridge";
import { useEffect } from "react";

const MyComponent = () => {
const { appBridge, appBridgeState } = useAppBridge();

useEffect(() => {
appBridge?.dispatch(/* Something */);
}, [appBridge]);

return <div>Current locale is: {appBridgeState?.locale}</div>;
};

appBridgeState? and appBridge can be nullish, because they don't exist in the server side context.

Events​

Events are messages that originate in Saleor Dashboard. AppBridge can subscribe to events and the App can react to them.

Subscribing to events​

subscribe(eventType, callback) - can be used to listen to particular event type. It returns an unsubscribe function, which unregister the callback.

Example:

const unsubscribe = appBridge.subscribe("handshake", (payload) => {
setToken(payload.token); // do something with event payload

const { token } = appState.getState(); // you can also get app's current state here
});

// unsubscribe when callback is no longer needed
unsubscribe();

Unsubscribing multiple listeners​

unsubscribeAll(eventType?) - unregister all callbacks of provided type. If no type was provided, it will remove all event callbacks.

Example:

// unsubscribe from all handshake events
appBridge.unsubscribeAll("handshake");

// unsubscribe from all events
appBridge.unsubscribeAll();

Available event types​

Event typeDescription
handshakeFired when iFrame containing the App is initialized or new token is assigned
responseFired when Dashboard responds to an Action
redirectFired when Dashboard changes a subpath within the app path
themeFired when Dashboard changes the theme
localeChangedFired when Dashboard changes locale (and passes locale code in payload)
tokenRefreshFired when Dashboard receives a new auth token and passes it to the app

See source code for detailed payload

Actions​

Actions expose a high-level API to communicate with Saleor Dashboard. They're exported under an actions namespace.

Available methods​

dispatch(action) - dispatches an Action. Returns a promise which resolves when action is successfully completed.

Example:

import { actions } from "@saleor/app-sdk/app-bridge";

const handleRedirect = async () => {
await appBridge.dispatch(actions.Redirect({ to: "/orders" }));

console.log("Redirect complete!");
};

handleRedirect();

Available actions​

ActionArgumentsDescription
Redirectto (string) - relative (inside Dashboard) or absolute URL path
newContext (boolean) - should open in a new browsing context
Notificationstatus (info / success / warning / error / undefined)
title (string / undefined) - title of the notification
text (string / undefined) - content of the notification
apiMessage (string / undefined) - error log from api
NotifyReadyInform Dashboard that AppBridge is ready
UpdateRoutingnewRoute - current path of App to be set in URL
RequestPermissions (>=3.15)permissions - array of Permissions you want to add, redirectPath - value of query param app will receive in ?redirectUrl= after user approves/rejects permissionsAsk Dashboard to give more permissions to the app. Dashboard will unmount app. After user approves or denies, Dashboard will redirect to redirectPath. If operation fails, ?error=REASON will be appended
PopupClose (>=3.22.31)Ask Dashboard to close the popup. If the app is running in a popup, Dashboard will close it. If the app is not in a popup, this action does nothing and responds with ok: true.
WidgetResizeheight - widget content height in pixels (positive, finite)Ask Dashboard to resize the widget iframe to match the app's content height. Only affects *_DETAILS_WIDGETS extensions. See Sizing sidebar widgets for the recommended helpers.
RefreshEntity (>=3.23.9)Ask Dashboard to refresh the entity active in the current context (e.g. the currently open Order or Product), without a full page reload. Requires @saleor/app-sdk 1.11 or newer and Saleor Dashboard 3.23.9 or newer.

Refreshing the active entity​

When an app mutates data that the Dashboard is currently displaying — for example, an order widget that adds a note or changes fulfillment — the Dashboard's view can become stale until the user manually reloads. Dispatch actions.RefreshEntity() to ask the Dashboard to re-fetch the entity active in the current context.

import { actions, useAppBridge } from "@saleor/app-sdk/app-bridge";

const { appBridge } = useAppBridge();

const handleSaved = async () => {
// ...perform the mutation that changes the open Order/Product...

await appBridge.dispatch(actions.RefreshEntity());
};

The action takes no arguments: the Dashboard already knows which entity is open in the current context (the mount where your app is rendered, such as ORDER_DETAILS_WIDGETS or PRODUCT_DETAILS_WIDGETS).

info

Requires @saleor/app-sdk 1.11 or newer. Dashboard support for handling this action was added in Saleor Dashboard 3.23.9 — coordinate SDK and Dashboard upgrades.

Sizing sidebar widgets​

Detail-page widgets (PRODUCT_DETAILS_WIDGETS, ORDER_DETAILS_WIDGETS, and other *_DETAILS_WIDGETS mounts) render in the entity sidebar. By default, each widget iframe keeps a fixed height, which can clip content or leave empty space when your UI is shorter or taller than that box.

When you report your widget's content height, the Dashboard resizes the iframe to match. Staff see the full widget without scrolling inside a tiny frame, and multiple app widgets stack naturally in the sidebar without internal scrollbars.

info

Requires @saleor/app-sdk 1.9 or newer. Height is sent as the widgetResize App Bridge action (actions.WidgetResize). The Dashboard must handle this action in its App Bridge message handler — coordinate SDK and Dashboard upgrades.

Reporting height is opt-in. Apps that do not use the helpers below keep the previous fixed height.

React apps​

Wrap your widget UI in an element with a ref, then call useWidgetAutoResize inside AppBridgeProvider (see AppBridgeProvider). The hook uses useAppBridge() internally, measures the root element, and dispatches WidgetResize when the layout changes (after App Bridge becomes ready).

import { useRef } from "react";
import { useWidgetAutoResize } from "@saleor/app-sdk/app-bridge";

export default function ProductWidget() {
const rootRef = useRef<HTMLDivElement>(null);

useWidgetAutoResize(rootRef);

return (
<div ref={rootRef}>
{/* Your widget content */}
</div>
);
}

Use this on routes served for target: "WIDGET" extensions on *_DETAILS_WIDGETS mounts. See Extending Dashboard with Apps for manifest setup.

Without the hook​

If you are not using React, or you need to report height after a one-off layout change (for example, when async data finishes loading), pass an AppBridge instance to the lower-level helpers (from useAppBridge() or your own new AppBridge()):

HelperUse when
postWidgetHeight(appBridge, root)You have a root element; measure it and dispatch WidgetResize in one call
reportWidgetHeight(appBridge, height)You already know the height in pixels
warning

Pass an element that wraps only your widget UI to postWidgetHeight. Do not measure document.body or document.documentElement — they match the iframe size, not your content, so reported heights stay too small and widgets stay clipped. The same applies if you pass a height to reportWidgetHeight that you calculated from those nodes.

Helpers no-op during SSR. postWidgetHeight requires a widget root element.

Protocol​

Height is reported through the standard App Bridge channel — reportWidgetHeight and postWidgetHeight call appBridge.dispatch(actions.WidgetResize({ height })), the same mechanism as redirect, notification, and other actions. If you prefer, you can dispatch it yourself:

import { actions } from "@saleor/app-sdk/app-bridge";

appBridge.dispatch(actions.WidgetResize({ height: 320 })).catch(() => {
// Best-effort: sizing helpers swallow rejections (timeout or negative response).
});

The helpers are the recommended path: they no-op during SSR, skip invalid heights (non-finite or non-positive), and attach a .catch so a missing or slow Dashboard response does not surface as an unhandled rejection.

Until the first report, the iframe uses a 200px default height. Reported heights are capped at 5000px.