Skip to main content

App Bridge Actions Reference

The App Bridge SDK exposes a class for each action family plus the raw dispatch / dispatchAndWait calls underneath. Use the classes when you can — they validate types, manage subscriptions, and handle cleanup. Drop to the raw API when you need an action the SDK hasn’t wrapped yet.

Calling Convention

The SDK signatures are:
app.dispatch(action: string, payload?: object): string
app.dispatchAndWait(action: string, payload?: object): Promise<object>
app.subscribe(action: string, callback: (payload, error?) => void): () => void
action is the string name (e.g. 'TOAST_SHOW'), not an object. The SDK wraps the payload into the wire format { type: 'APP_BRIDGE_ACTION', action, payload, id } and posts it to the host. The host responds with { type: 'APP_BRIDGE_RESPONSE', action, id, payload, error? }.
import { createApp, Toast, Modal, ResourcePicker } from '@launchmystore/app-bridge';

const app = createApp({
  apiKey: 'your-client-id',
  host: new URLSearchParams(location.search).get('host'),
});

// Class-based — preferred
Toast.success(app, 'Saved!');

// Raw — equivalent
app.dispatch('TOAST_SHOW', { message: 'Saved!', type: 'success' });

// Request/response — class
const result = await ResourcePicker.create(app, { resourceType: 'product' }).dispatch();

// Request/response — raw
const result = await app.dispatchAndWait('RESOURCE_PICKER_OPEN', { resourceType: 'product' });

Action Catalogue

FamilyClassAction names
ToastToastTOAST_SHOW, TOAST_DISMISS
ModalModalMODAL_OPEN, MODAL_CLOSE
Resource PickerResourcePickerRESOURCE_PICKER_OPEN, RESOURCE_PICKER_CLOSE
Title BarTitleBarTITLE_BAR_UPDATE
Navigation MenuNavigationMenuNAV_MENU_UPDATE
Contextual Save BarContextualSaveBarCONTEXTUAL_SAVE_BAR
RedirectRedirectREDIRECT
Loading(raw)LOADING_START, LOADING_STOP
FullscreenFullscreenFULLSCREEN_ENTER, FULLSCREEN_EXIT, FULLSCREEN_TOGGLE
Leave ConfirmationLeaveConfirmationLEAVE_CONFIRMATION_ENABLE, LEAVE_CONFIRMATION_DISABLE
Session TokenSessionTokenSESSION_TOKEN_REQUEST
UserUserUSER_FETCH
ConfigConfigCONFIG_FETCH
EnvironmentEnvironmentENVIRONMENT_FETCH
FeaturesFeaturesFEATURES_QUERY
ClipboardClipboardCLIPBOARD_WRITE, CLIPBOARD_READ_REQUEST
ScannerScannerSCANNER_OPEN, SCANNER_CLOSE
HistoryHistoryHISTORY_PUSH, HISTORY_REPLACE, HISTORY_BACK, HISTORY_FORWARD, HISTORY_GO
PrintPrintPRINT
ShareShareSHARE
LifecycleLifecycleLIFECYCLE_MOUNT, LIFECYCLE_UNMOUNT
Cart (checkout/post-purchase)CartCART_LINES_CHANGE, DISCOUNT_CODE_CHANGE, GIFT_CARD_CHANGE, NOTE_CHANGE, ATTRIBUTE_CHANGE
Buyer Journey (checkout)BuyerJourneyBUYER_JOURNEY_INTERCEPT_REQUEST, BUYER_JOURNEY_INTERCEPT_RESPONSE
Cart and BuyerJourney are checkout-only. They’re documented in detail on the Checkout App Bridge page. Everything else runs in the admin host.

UI Actions

Toast (TOAST_SHOW)

import { Toast } from '@launchmystore/app-bridge';

// Convenience helpers
Toast.success(app, 'Changes saved!');
Toast.error(app, 'Save failed');
Toast.warning(app, 'Stock low');
Toast.info(app, 'Sync in progress');

// Full control
Toast.create(app, {
  message: 'Saved with tag',
  type: 'success',
  duration: 4000,
  action: { label: 'Undo', onAction: () => undoChange() },
  onDismiss: () => console.log('toast closed'),
}).dispatch();

// Raw equivalent
app.dispatch('TOAST_SHOW', {
  message: 'Saved!',
  type: 'success',
  duration: 4000,
});
FieldTypeDescription
messagestringUp to 200 chars (longer is truncated)
type'success' | 'error' | 'warning' | 'info'Default info
durationnumberms; default 4000
actionobjectOptional { label, onAction } button
onDismissfunctionFired when toast closes
import { Modal } from '@launchmystore/app-bridge';

const result = await Modal.create(app, {
  title: 'Delete Product?',
  message: 'This cannot be undone.',
  primaryAction: { content: 'Delete', destructive: true },
  secondaryAction: { content: 'Cancel' },
}).dispatch();

if (result.action === 'primary') {
  // user clicked Delete
}

// Raw
const result2 = await app.dispatchAndWait('MODAL_OPEN', {
  title: 'Confirm',
  message: 'Are you sure?',
  primaryAction: { content: 'OK' },
});
Response: { action: 'primary' | 'secondary' | 'dismissed' }

Loading (LOADING_START / LOADING_STOP)

app.dispatch('LOADING_START');
await save();
app.dispatch('LOADING_STOP');

Fullscreen (FULLSCREEN_ENTER / EXIT / TOGGLE)

import { Fullscreen, FullscreenAction } from '@launchmystore/app-bridge';

Fullscreen.create(app, FullscreenAction.Enter).dispatch();
// or
app.dispatch('FULLSCREEN_ENTER');

Title Bar (TITLE_BAR_UPDATE)

import { TitleBar } from '@launchmystore/app-bridge';

TitleBar.create(app, {
  title: 'Product Reviews',
  primaryAction: { content: 'Add Review', onAction: 'ADD_REVIEW' },
  secondaryActions: [
    { content: 'Export', onAction: 'EXPORT' },
    { content: 'Settings', onAction: 'SETTINGS' },
  ],
  breadcrumbs: [
    { content: 'Apps', url: '/admin/apps' },
    { content: 'My App', url: '/admin/apps/my-app' },
  ],
}).dispatch();

// Listen for button clicks
app.subscribe('ADD_REVIEW', () => console.log('Add clicked'));

Redirect (REDIRECT)

import { Redirect } from '@launchmystore/app-bridge';

Redirect.create(app, { url: '/admin/products/123' }).dispatch();
// or shorthand
app.redirect.dispatch({ url: '/admin/products/123' });
// or raw
app.dispatch('REDIRECT', { url: '/admin/products/123' });

// External
app.dispatch('REDIRECT', {
  url: 'https://example.com',
  external: true,
  newTab: true,
});
import { NavigationMenu } from '@launchmystore/app-bridge';

NavigationMenu.create(app, {
  items: [
    { label: 'Dashboard', destination: '/' },
    { label: 'Reviews', destination: '/reviews' },
    { label: 'Settings', destination: '/settings' },
  ],
  active: '/reviews',
}).dispatch();

History (HISTORY_PUSH / REPLACE / BACK / FORWARD / GO)

import { History } from '@launchmystore/app-bridge';

History.create(app).push({ path: '/reviews?filter=pending' });
History.create(app).replace({ path: '/reviews' });
History.create(app).back();
History.create(app).forward();
History.create(app).go(-2);

Resource Picker (RESOURCE_PICKER_OPEN / CLOSE)

import { ResourcePicker } from '@launchmystore/app-bridge';

const result = await ResourcePicker.create(app, {
  resourceType: 'product',
  multiple: true,
  selectionIds: ['prod_123', 'prod_987'],   // preferred
  filter: { status: 'active' },
}).dispatch();

if (!result.cancelled) {
  console.log('Selected:', result.selection);
}
multiple: true lets the picker return more than one resource. Pass selectionIds (an array of ids) to pre-select. Legacy initialSelection: [{ id }] still works but selectionIds is the recommended shape going forward. On cancel the picker returns { cancelled: true, selection: [] }selection is always an array.
Resource TypeDescription
productProducts
product_variantProduct variants
collectionCollections
customerCustomers
orderOrders
blogBlogs
articleBlog articles
pagePages
menuNavigation menus
fileFiles / media — browse and upload (no app scope required; runs under the merchant admin session)
metaobjectMetaobjects
Response: { cancelled: boolean, selection: [...] }selection is always an array (length 1 for single-select). For non-file types each element is a stable handle string: handle for product / collection / blog / article / page / menu, email for customer, order_number for order, id for product_variant. Store the handle; resolve extra fields server-side. For file each element is a rich object so you get a usable URL directly — no extra lookup needed:
{ "id": "<mediaId>", "url": "https://cdn…/asset.png", "filename": "asset.png", "mimeType": "image/png" }
The file picker is the full Media Library — the merchant can search, browse, and upload new files in-place, then pick one or more. multiple: true returns several objects. id is the stable mediaId; url is a permanent CDN URL. Other resource types select existing records only.

Save Bar (CONTEXTUAL_SAVE_BAR)

import { ContextualSaveBar, SaveBarAction } from '@launchmystore/app-bridge';

const bar = ContextualSaveBar.create(app, {
  saveAction: { content: 'Save', onAction: 'SAVE' },
  discardAction: { content: 'Discard', onAction: 'DISCARD' },
  fullWidth: false,
});
bar.show();

app.subscribe('SAVE', async () => {
  await saveChanges();
  bar.hide();
});
app.subscribe('DISCARD', () => {
  discardChanges();
  bar.hide();
});
The raw payload is { action: 'SHOW' | 'HIDE', ... } — use the class.

Leave Confirmation (LEAVE_CONFIRMATION_ENABLE / DISABLE)

import { LeaveConfirmation } from '@launchmystore/app-bridge';

LeaveConfirmation.create(app, {
  message: 'You have unsaved changes. Are you sure you want to leave?',
}).enable();

// later
LeaveConfirmation.create(app).disable();

Session & Auth (SESSION_TOKEN_REQUEST)

// Preferred — handles caching + auto-refresh
const token = await app.getSessionToken();

fetch('/api/v1/data', {
  headers: { Authorization: `Bearer ${token}` },
});
Raw dispatchAndWait('SESSION_TOKEN_REQUEST') returns { token, expires } but the wrapper caches the token to avoid round-trips on every API call.

User / Config / Environment / Features

import { User, Config, Environment, Features } from '@launchmystore/app-bridge';

const user = await User.create(app).fetch();
// { id, email, name, roles }

const config = await Config.create(app).fetch();
// { apiKey, shopDomain, plan }

const env = await Environment.create(app).fetch();
// { locale, timezone, mobile }

const flags = await Features.create(app).query();
// { contextual_save_bar: true, resource_picker: true, ... }
Raw action names: USER_FETCH, CONFIG_FETCH, ENVIRONMENT_FETCH, FEATURES_QUERY.

Browser APIs

Clipboard (CLIPBOARD_WRITE / CLIPBOARD_READ_REQUEST)

import { Clipboard } from '@launchmystore/app-bridge';

await Clipboard.write(app, 'Copied!');
const text = await Clipboard.read(app);
Reads go through the host because Chrome blocks clipboard-read in cross-origin iframes. Writes (copy()) work iframe-side directly.

Scanner (SCANNER_OPEN / SCANNER_CLOSE)

import { Scanner } from '@launchmystore/app-bridge';

const result = await Scanner.create(app).open();
if (!result.cancelled) console.log('Scanned:', result.data);
Mobile-only. Desktop hosts respond with cancelled: true.
import { Print } from '@launchmystore/app-bridge';

Print.create(app).dispatch();
// or
app.dispatch('PRINT');

Share (SHARE)

import { Share } from '@launchmystore/app-bridge';

Share.create(app, {
  title: 'Check out this product',
  url: 'https://store.com/products/widget',
  text: 'Best socks ever',
}).dispatch();

Lifecycle

import { Lifecycle } from '@launchmystore/app-bridge';

Lifecycle.create(app).onMount(() => loadData());
Lifecycle.create(app).onUnmount(() => saveState());
Raw event names: LIFECYCLE_MOUNT, LIFECYCLE_UNMOUNT.

Checkout-only Surfaces

The following actions are wired into the customer-checkout host and the post-purchase host. See Checkout App Bridge for the full contract.

Cart (CART_LINES_CHANGE, DISCOUNT_CODE_CHANGE, NOTE_CHANGE, ATTRIBUTE_CHANGE, GIFT_CARD_CHANGE)

import { Cart } from '@launchmystore/app-bridge';

const cart = Cart.create(app);

await cart.applyCartLinesChange({
  type: 'addCartLine',
  merchandiseId: 'gid://launchmystore/Variant/abc-123',
  quantity: 1,
});

await cart.applyDiscountCodeChange({
  type: 'addDiscountCode',
  code: 'WELCOME10',
});

await cart.applyNoteChange({
  type: 'updateNote',
  note: 'Leave with concierge',
});

await cart.applyAttributeChange({
  type: 'updateAttribute',
  key: 'gift_wrap',
  value: 'true',
});

Buyer Journey (BUYER_JOURNEY_INTERCEPT_REQUEST/RESPONSE)

Block the customer from advancing to payment until your condition is met.
import { BuyerJourney } from '@launchmystore/app-bridge';

const journey = BuyerJourney.create(app);
const unsubscribe = journey.intercept(() => {
  if (!termsAccepted) {
    return {
      behavior: 'block',
      reason: 'You must accept the terms before continuing.',
    };
  }
  return { behavior: 'allow' };
});

// later
unsubscribe();

Error Handling

try {
  const result = await app.dispatchAndWait('RESOURCE_PICKER_OPEN', {
    resourceType: 'product',
  });
} catch (error) {
  // App Bridge errors arrive as Error('App Bridge: ACTION failed: reason')
  if (error.message.includes('timeout')) {
    // Host did not respond — likely action not supported on this host
  } else {
    console.error(error);
  }
}
The SDK rejects with a timeout error after 10 seconds if the host never responds. That’s the most common failure mode when calling an admin-only action from a checkout host (or vice versa) — check the table at the top of this page for which host each action is wired to.

Complete Example

import {
  createApp, Toast, ResourcePicker, ContextualSaveBar,
} from '@launchmystore/app-bridge';

const app = createApp({
  apiKey: 'your-client-id',
  host: new URLSearchParams(location.search).get('host'),
});

document.getElementById('add-review').addEventListener('click', async () => {
  const picker = await ResourcePicker.create(app, {
    resourceType: 'product',
  }).dispatch();
  if (picker.cancelled || picker.selection.length === 0) return;

  app.dispatch('LOADING_START');
  try {
    const token = await app.getSessionToken();
    await fetch('/api/reviews', {
      method: 'POST',
      headers: {
        Authorization: `Bearer ${token}`,
        'Content-Type': 'application/json',
      },
      body: JSON.stringify({ productId: picker.selection[0].id }),
    });
    Toast.success(app, 'Review added!');
  } catch (err) {
    Toast.error(app, 'Failed to add review');
  } finally {
    app.dispatch('LOADING_STOP');
  }
});

I18n (I18N_UPDATE)

The host pushes the merchant’s admin locale to your iframe and re-pushes on every change. Subscribe via the I18n action class — it caches the last dictionary and exposes translate / formatNumber / formatDate helpers backed by Intl.*.
import { I18n } from '@launchmystore/app-bridge';

const i18n = I18n.create(app);
i18n.subscribe(({ locale, dictionary }) => {
  console.log('Locale changed to', locale);
});

// Plain interpolation map (no reserved keys → second arg is treated as vars).
const greeting = i18n.translate('hello', { name: 'Alex' });

// Options form: supply a fallback string when the key is missing AND
// interpolation vars under `vars`. The fallback wins over the bare key.
const label = i18n.translate('settings.title', {
  default: 'Settings',
  vars: { user: 'Alex' },
});

const price = i18n.formatNumber(19.99, { style: 'currency', currency: 'USD' });
The wire payload is { locale: string, dictionary: Record<string,string> }. The host derives locale from the admin’s cookie/localStorage and the dictionary from /api/i18n/admin?locale=... (silently {} if the endpoint is absent — your app should fall back to Intl.* for formatting).

REST API (CONFIG_GET + session tokens)

Use the RestApi action class to call the LaunchMyStore REST API (/api/v1/...) with an automatically-attached session token. The host exposes the API base URL via CONFIG_GET so your iframe doesn’t have to hardcode api.launchmystore.io.
import { RestApi } from '@launchmystore/app-bridge';

const api = RestApi.create(app);
const { products } = await api.fetchJson('/api/v1/products?limit=50');

// Plain-object bodies are auto-JSON-encoded; `Content-Type` is set unless
// you provide one yourself. Pass a string / FormData / Blob to opt out.
await api.fetch('/api/v1/products/123', {
  method: 'PUT',
  body: { title: 'New title' },
});
fetch() resolves to the raw Response. fetchJson<T>() parses JSON and returns the typed body. Token + apiBase are resolved lazily on the first call and cached.

Intents (INTENT_LAUNCH)

Launch another app’s admin action from inside your own — for example, chain a “Send invoice” action after editing an order.
import { Intents } from '@launchmystore/app-bridge';

const intents = Intents.create(app);
const result = await intents.launchAndWait('invoice-app.send-invoice', {
  orderId: 'gid://launchmystore/Order/123',
});

if (result.launched) {
  console.log('Opened intent', result.target);
}
The target string is <appHandle>.<extensionHandle> — the host resolves it against the merchant’s registered admin extensions and opens the matching admin_action modal. Missing targets return { error: '...' }.

Action constants

The SDK does not export a centralised ACTIONS enum — every action name is a plain string passed to app.dispatch / app.dispatchAndWait. The canonical source of truth for each name is the corresponding action class itself:
import {
  Toast,             // 'TOAST_SHOW', 'TOAST_DISMISS'
  Modal,             // 'MODAL_OPEN', 'MODAL_CLOSE'
  ResourcePicker,    // 'RESOURCE_PICKER_OPEN', 'RESOURCE_PICKER_CLOSE'
  TitleBar,          // 'TITLE_BAR_UPDATE'
  NavigationMenu,    // 'NAV_MENU_UPDATE'
  ContextualSaveBar, // 'CONTEXTUAL_SAVE_BAR'
  Redirect,          // 'REDIRECT'
  Fullscreen,        // 'FULLSCREEN_ENTER' | 'EXIT' | 'TOGGLE'
  LeaveConfirmation, // 'LEAVE_CONFIRMATION_ENABLE' | 'DISABLE'
  Scanner,           // 'SCANNER_OPEN', 'SCANNER_CLOSE'
  User,              // 'USER_FETCH'
  Config,            // 'CONFIG_FETCH' (admin) / 'CONFIG_GET' (RestApi)
  Environment,       // 'ENVIRONMENT_FETCH'
  Features,          // 'FEATURES_QUERY'
  Clipboard,         // 'CLIPBOARD_WRITE' (iframe), 'CLIPBOARD_READ_REQUEST'
  Print,             // 'PRINT'
  Share,             // 'SHARE'
  History,           // 'HISTORY_PUSH' | 'REPLACE' | 'BACK' | 'FORWARD' | 'GO'
  Lifecycle,         // 'LIFECYCLE_MOUNT', 'LIFECYCLE_UNMOUNT'
  SessionToken,      // 'SESSION_TOKEN_REQUEST'
  Cart,              // 'CART_LINES_CHANGE' | 'DISCOUNT_CODE_CHANGE'
                     // | 'GIFT_CARD_CHANGE'  | 'NOTE_CHANGE'
                     // | 'ATTRIBUTE_CHANGE'  | 'METAFIELD_CHANGE'
  BuyerJourney,      // 'BUYER_JOURNEY_INTERCEPT_REQUEST/RESPONSE'
  I18n,              // 'I18N_UPDATE'           (host-pushed)
  RestApi,           // 'CONFIG_GET' (resolves apiBase)
  Intents,           // 'INTENT_LAUNCH'
} from '@launchmystore/app-bridge';
Features.APP_BRIDGE_FEATURES (re-exported from the package root) is the one named-constant the SDK ships — a string-literal union of every feature flag a host may advertise via FEATURES_QUERY. Use it in discriminated checks:
import { APP_BRIDGE_FEATURES } from '@launchmystore/app-bridge';

const { features } = await Features.create(app).query();
if (features.includes(APP_BRIDGE_FEATURES.RESOURCE_PICKER)) {
  // Safe to call ResourcePicker
}
If you need a raw string action name that no class wraps yet (rare — this only happens for in-flight features) call app.dispatch(name, ...) directly. Action names are case-sensitive and the host silently ignores unknown names, so a typo will surface as a 10-second timeout in dispatchAndWait. See Error Handling for the recovery pattern.

See Also