Admin App Bridge helps your embedded app feel at home inside the Tringify admin. It gives your app the current store context and lets it use trusted admin UI such as title-bar actions, save controls, dialogs, notifications, navigation, and resource pickers.
1. Add the SDK#
Add the major-version URL to the page configured as your embedded admin URL. The @1 release line receives compatible fixes and improvements without taking your app across a breaking major version.
<script src="https://bridge.tringify.com/app-bridge@1.js"></script>
2. Connect to the admin#
Create the shared bridge instance, wait for the Tringify admin to be ready, and then read the current context. Run this when your embedded page starts.
const bridge = TringifyBridge.init();
await bridge.ready();
const context = await bridge.getContext();
console.log(`Opened ${context.store_name}`);
getContext() returns host-owned, read-only context. Nullable store values remain null; the SDK does not invent replacements.
Field |
Meaning |
|---|---|
store_id |
The store currently being managed. |
app_id |
Your installed app. |
host |
The Tringify host that opened the app. |
store_name |
The store display name, or |
primary_domain |
The store primary domain, or |
timezone |
The store IANA timezone. |
currency |
The store currency code, or |
locale |
The active admin locale. |
scopes |
The app scopes available to the current bridge session. |
theme |
|
expires_at |
When the current bridge session expires. |
bridge_major |
The active App Bridge major version. |
bridge_fingerprint |
The exact host contract fingerprint. |
bridge_operations |
The host operations available in this session. |
After ready() resolves, use bridge.supports(operation) when a screen can make sensible use of an optional host capability. It returns true only when that operation is declared by the current host. Calling an unsupported operation still rejects; capability checks do not create a fallback implementation.
await bridge.ready();
if (bridge.supports("resourcePicker.open")) {
showResourceSelection();
}
Inspect the loaded SDK#
TringifyBridge.major is the loaded contract major, TringifyBridge.version is the exact SDK release, and TringifyBridge.contractFingerprint identifies the generated contract bundled into that file. These values are useful for diagnostics; do not branch business behavior on the patch version.
bridge.launch is available synchronously with { storeId, appId, host, bridgeMajor }. It contains identifiers only, never the launch credential. Prefer getContext() after ready() for host-owned store context.
console.log({
sdk: TringifyBridge.version,
major: TringifyBridge.major,
contract: TringifyBridge.contractFingerprint,
host: bridge.launch.host,
});
3. Add native admin UI#
Your app owns the content inside its frame. Admin App Bridge owns the surrounding Tringify experience. Start with a page title and use host feedback for actions the merchant should notice.
await bridge.titleBar.set({ title: "Campaign settings" });
async function saveSettings() {
// Save your app's settings.
await bridge.toast("Settings saved", "success");
}
The bridge also provides save-state controls, banners, loading feedback, navigation, fullscreen mode, dialogs, resource pickers, and template editors. Each feature is documented in the pages that follow.
4. Call the Store API#
Admin App Bridge handles the embedded experience. Store data comes from the Store API. Ask for a short-lived session token containing only the scopes needed by the current screen, then use it as a bearer token.
const session = await bridge.getSessionToken({
scopes: ["read_products"],
});
const response = await fetch(
"https://api-store.tringify.com/api/2025-01/products",
{ headers: { Authorization: `Bearer ${session.token}` } },
);
const body = await response.json();
if (!response.ok) {
throw new Error(body.error.message);
}
const products = body.data.products;
A session token cannot exceed the permissions approved during installation or the permissions of the merchant opening the app. Request a session token before each related batch of Store API work instead of persisting it; the SDK reuses a valid session for the same scope set, so repeated requests are inexpensive.
5. Handle startup clearly#
Show a useful app-owned error state if startup fails. This gives the merchant a clear next step when the app URL, allowed origin, installation, or requested permissions need attention.
async function start() {
try {
const bridge = TringifyBridge.init();
await bridge.ready();
const context = await bridge.getContext();
await bridge.titleBar.set({ title: "My app" });
renderApp(context);
} catch (error) {
renderStartupError(error);
}
}
start();
Before you ship#
Use HTTPS and register the exact origins that host your embedded app.
Load a major-pinned or exact-version SDK URL; never use an unversioned production dependency.
Request only the Store API scopes needed by the current screen.
Match the
themeandlocalesupplied by the host, and respond to later theme changes.Handle rejected bridge and Store API calls with a clear merchant-facing state.
Test installation, startup, save and discard behavior, permission failures, and app closure before submitting a release.
Link to Tringify-hosted Pricing#
Use bridge.billing.openPricing() to open Tringify's Pricing page for the current app and store. It works in Store Admin and in the iOS and Android apps. The page has Plans, Usage and Credits tabs. Only the store owner can manage billing. Keep Pricing out of your app's nav.set items: this page belongs to Tringify, outside your embedded app. Offer an explicit “Manage pricing” action in Settings or another relevant screen. Do not use routing.setPath('/plan'), which changes the app's own route.
Style the action with UI Kit's tb-btn tb-btn-outline classes. After await bridge.ready(), hide the button if !bridge.supports('navigate'); otherwise call bridge.billing.openPricing() from its click handler and handle promise errors through your normal app feedback. The host checks unsaved changes before leaving the app. See App Bridge getting started and UI Kit.
Verify requests to your own backend#
When your admin page calls your own server, send await bridge.getIdentityToken() as Authorization: Bearer <token> (SDK 1.0.64+). It is an RS256 JWT signed by Tringify that names the store (store_id), your app (app_id) and the person who opened it (sub). It lasts about ten minutes; the SDK refreshes it before it expires.
Verify it on your server before trusting it. Read the keys from https://api.tringify.com/oauth/jwks, keep them by kid, and fetch again when a token names a key you don't have. Check that the algorithm is RS256, iss is https://accounts.tringify.com, aud is your client ID, purpose is app_bridge_session, and exp is in the future. Then use store_id and app_id.
This needs no Store API call, so it never spends the store's rate limit. Don't call GET /discover on every request to identify a session. The identity token grants nothing by itself: read and change store data with your installation token, or with getSessionToken() in the page.
Open an outside page#
Some pages refuse to load inside Store Admin, a provider's sign-in page among them: your app's frame is sandboxed, and so is any window it opens. Use bridge.openExternal(url) (SDK 1.0.70+) to have the host open the page instead. Store Admin opens it in a new browser window outside the admin, with no handle back to it; iOS and Android open it in the system browser.
Declare every origin you open in app_bridge_external_origins in tringify.app.json: bare https origins such as https://www.facebook.com, at most 10. The host checks the origin against the installed release and refuses anything else with ORIGIN_NOT_DECLARED, so stores get a new origin only when they accept a release that declares it.
button.addEventListener("click", async () => {
const { opened } = await bridge.openExternal(signInURL);
if (!opened) bridge.toast("Allow pop-ups for the admin, then try again.", "error");
});
Call it from a click handler: browsers block windows opened without one, and then opened is false. The new window cannot message your app, so learn the outcome from your own backend, for example by polling your API until the sign-in completes. Check bridge.supports('external.open') before showing the action.