Dialogs and pickers

Open trusted Tringify UI for decisions, resource selection, and supported editors.

Use hosted UI when a merchant needs to confirm a decision or choose store data. Your app makes one App Bridge call; Tringify opens a familiar, trusted surface above the embedded app and returns the merchant's answer. Your app does not need to recreate StoreAdmin or mobile picker UI.

How hosted UI works#

Step

What happens

  1. Request

Your embedded app calls a supported bridge operation.

  1. Validate

The loaded SDK and host validate the operation, resource, options, app scopes, and merchant permissions.

  1. Choose

Tringify renders the dialog, picker, or editor above the app. The host owns resource rows, search, filters, ordering, and pagination.

  1. Return

The promise settles after the merchant confirms, cancels, dismisses, or an error closes the surface.

StoreAdmin and Tringify Mobile#

StoreAdmin draws hosted controls above the embedded app. Updated Tringify Mobile hosts provide the same version 1 operations, including dialogs, pickers, message editors, navigation, title actions, banners, save bars, and fullscreen. Older mobile installations can support fewer operations. Check the loaded bridge after ready() instead of inferring support from the device.

Operation

StoreAdmin

Tringify Mobile (iOS and Android)

modal.open

Available

Check bridge.supports() on older installations

modal.confirm

Available

Check bridge.supports() on older installations

resourcePicker.open

Available

Check bridge.supports() on older installations

templateEditor.open

Available

Check bridge.supports() on older installations

mediaPicker.open

Available

Check bridge.supports() on older installations

app.js javascript
const canPickTags =
  bridge.supports("resourcePicker.open") &&
  bridge.resourcePicker.RESOURCES.includes("tag");

if (!canPickTags) {
  // Keep the action hidden or explain that this host does not support it.
  return;
}

Open a dialog#

bridge.modal.open(options) opens a flexible StoreAdmin dialog. title is required and accepts up to 120 characters. body is optional and accepts up to 2,000 characters. primaryAction is an optional { content, destructive? } object and secondaryAction is an optional { content } object; action labels accept up to 80 characters. The promise resolves with { action: "primary" | "secondary" | "dismiss" }.

javascript
if (bridge.supports("modal.open")) {
  const result = await bridge.modal.open({
    title: "Publish this campaign?",
    body: "Customers will begin receiving it immediately.",
    primaryAction: { content: "Publish" },
    secondaryAction: { content: "Not yet" },
  });

  if (result.action === "primary") {
    await publishCampaign();
  }
}

Ask for confirmation#

bridge.modal.confirm(options) is the shorter StoreAdmin choice for yes or no. title, confirmText, and cancelText are required. body is optional; destructive is optional and defaults to false. Titles accept up to 120 characters, bodies up to 2,000, and button labels up to 80. Tringify never invents visible button copy for your app. The promise resolves to true only when the merchant confirms.

javascript
if (bridge.supports("modal.confirm")) {
  const confirmed = await bridge.modal.confirm({
    title: "Disconnect this channel?",
    body: "Product syncing will stop until it is connected again.",
    confirmText: "Disconnect",
    cancelText: "Keep connected",
    destructive: true,
  });

  if (confirmed) {
    await disconnectChannel();
  }
}

Choose store resources#

bridge.resourcePicker.open(options) opens a searchable, paginated Tringify picker in StoreAdmin and Tringify Mobile. Catalog pickers include Products, Product Attributes, Product Categories, Brands, Collections, Product Tags, and Vendors. Products, Brands, Vendors, Categories, Collections, Product Attributes, and Product Tags show their catalog icons in the host. The Product Attributes picker offers 10, 20, 50, 100, 200, or 500 rows per page. The attribute_option picker lists attributes; the merchant opens one, such as Colour, and chooses its options, the way a variable product opens into its variants. Each selected option includes its attributeId. Your app chooses the resource and may provide an initial query, saved selection, or declared initial filter values. Tringify fetches rows using the current store, app scopes, and merchant permissions. Browse results remain inside the host; your app receives only the confirmed selection.

Field

Required

Meaning

resource

yes

A value present in bridge.resourcePicker.RESOURCES.

selectionMode

no

single by default, or multiple when declared by the loaded contract.

query

no

Search text shown when the picker opens. The resource-specific limit is listed below.

initialSelectionIds

no

Saved opaque picker IDs to resolve again. Supply at most one in single mode or the resource-specific limit in multiple mode.

filters

no

Initial values for filters declared for that resource. Unknown filter names and values are rejected.

The host performs search and pagination automatically; apps do not pass a page number or cursor. Previous and Next stay outside the scrolling rows. Selections persist across pages, searches, and filters. The selected count opens a review of every chosen item, including other pages; merchants can remove individual items or clear the selection. StoreAdmin also supports Shift-click range selection in multiple mode. Reaching the limit prevents adding another item, while variable products remain openable so a chosen variant can be removed. Select returns the confirmed items once; Cancel or dismissal returns an empty selection.

Supported resources and limits#

Resource

Required app scope

Multiple-selection limit

Query limit

Declared filters

attribute

read_products

50

200

None

attribute_option

read_products

50

200

None

brand

read_products

50

200

None

category

read_products

50

200

type

collection

read_products

50

200

None

customer

read_customers

50

200

status, marketing

location

read_locations

20

200

None

inventory_group

read_inventory

50

200

None

metafield_definition

read_products

100

255

owner_type

order

read_orders

50

200

archive, payment, fulfillment

product

read_products

50

255

type, status

tag

read_products

50

200

None

vendor

read_products

50

200

None

media

read_files

200

200

media_type: all, image, video, document, 3d

site_asset

read_site_assets

200

200

media_type: all, image, video, document, 3d

app.js javascript
const { selection } = await bridge.resourcePicker.open({
  resource: "tag",
  selectionMode: "multiple",
  initialSelectionIds: savedTagIds,
});

const tagIds = selection.map((tag) => tag.id);

Pick Categories, Brands, and Collections#

app.js javascript
const catalogResources = ["category", "brand", "collection"]
  .filter((resource) => bridge.resourcePicker.RESOURCES.includes(resource));

if (!bridge.supports("resourcePicker.open") || catalogResources.length !== 3) {
  return;
}

const { selection: collections } = await bridge.resourcePicker.open({
  resource: "collection",
  selectionMode: "multiple",
  initialSelectionIds: savedCollectionIds,
});

Pick Product Attributes#

Use resource: "attribute" to select Product Attribute definitions such as Color, Size, Material, or Pattern—not their option values. It requires read_products and the merchant opening the app must be allowed to read products. StoreAdmin and Tringify Mobile own search and pagination, support single or multiple selection, and resolve saved initialSelectionIds up to the contract maximum. Each confirmed item contains the stable store-scoped attribute id, its title, slug in subtitle, and attribute subtype in kind.

app.js javascript
if (!bridge.resourcePicker.RESOURCES.includes("attribute")) {
  return;
}

const { selection } = await bridge.resourcePicker.open({
  resource: "attribute",
  selectionMode: "multiple",
  initialSelectionIds: savedAttributeIds,
});

const attributeIds = selection.map((attribute) => attribute.id);

Pick Product Tags and Vendors#

Product Tags and Vendors are separate picker resources. Both require read_products, work in StoreAdmin and Tringify Mobile, support single or multiple selection, and return opaque IDs that can be saved in initialSelectionIds. The host owns search and pagination.

app.js javascript
if (!bridge.resourcePicker.RESOURCES.includes("vendor")) {
  return;
}

const { selection } = await bridge.resourcePicker.open({
  resource: "vendor",
  selectionMode: "multiple",
  initialSelectionIds: savedVendorIds,
});

Products and variants#

Use the product resource for products and variants. A simple product can be selected immediately. Opening a variable product shows its variants in the same picker so the merchant can choose the exact variant. There is no separate variant resource.

app.js javascript
const result = await bridge.resourcePicker.open({
  resource: "product",
  selectionMode: "multiple",
  filters: { status: "active" },
});

for (const item of result.selection) {
  console.log(item.productId, item.variantId);
}

Product attributes#

Use resource: "attribute" when a merchant must map a provider field such as color, size, material, or pattern to a store Product Attribute. The selected id identifies the attribute definition, not one of its option values.

Pick locations#

Use resource: "location" to choose locations visible to the installed app. It requires read_locations, and the merchant opening the app must also be allowed to read locations. Results include merchant-managed locations and locations owned by the current app; locations owned by another app are never returned. The picker includes active and inactive visible locations, follows fulfillment priority, and searches the same name, slug, city, state/province, and address fields as the Location Store API. Each result returns the stable location id; kind is merchant or app so your app can retain the ownership distinction.

app.js javascript
if (!bridge.resourcePicker.RESOURCES.includes("location")) {
  return;
}

const { selection } = await bridge.resourcePicker.open({
  resource: "location",
  selectionMode: "multiple",
  initialSelectionIds: savedLocationIds,
});

const locations = selection.map(({ id, kind }) => ({ id, owner: kind }));

Uninstalling the owning app deactivates its managed locations. Reinstalling does not reactivate them automatically. Saved selections are resolved again whenever the picker opens, so deleted, inaccessible, or other-app locations are omitted.

Pick inventory groups#

Use resource: "inventory_group" to choose inventory groups. It requires read_inventory, and the merchant opening the app must also be allowed to view inventory. Search matches a group's name, slug, and SKU; results are sorted by name and show each group's SKU. Each result returns the stable inventory group id. Saved selections are resolved again whenever the picker opens, so deleted groups are omitted.

app.js javascript
if (!bridge.resourcePicker.RESOURCES.includes("inventory_group")) {
  return;
}

const { selection } = await bridge.resourcePicker.open({
  resource: "inventory_group",
  selectionMode: "multiple",
  initialSelectionIds: savedGroupIds,
});

const groupIds = selection.map(({ id }) => id);

Selection, cancellation, and errors#

Most results contain { id, title, subtitle? }. Product results also contain productId and, when a variant was selected, variantId. Treat id as the opaque picker identity used for later preselection. Use productId and variantId with product APIs. Media selections include url, fileType, mediaType, and mimeType, plus previewUrl and altText when available.

The host shows the current page and matching-record range. Products, Brands, Vendors, and Product Tags offer 20, 50, 100, 200, or 500 records per page. Categories and Collections also offer 10 records per page, their opening size. When a merchant opens a variable product, its variant list separately offers 20 or 50 variants per page. Changing the page size or search starts again at page 1; choices remain selected across pages and searches. Product Tags and Vendors use cursor-based Previous and Next controls. Products, Brands, Categories, and Collections can also offer page jumps when the total is known. Apps can set the initial query, but the host owns browsing and pagination.

Show saved IDs without opening a picker#

bridge.resources.resolve({ resource, ids }) turns picker IDs your app has already saved into current display items, without opening a dialog. Use it to show product titles, customer names, or file details for records your app stores by ID. It accepts every resource in bridge.resourcePicker.RESOURCES, applies the same app scope and merchant permission checks as the picker, and resolves with { items } in the same shape as picker results. Pass the opaque id values the picker returned, such as product:123 or variant:456.

Each call accepts up to 200 IDs for every resource. This is separate from the multiple-selection limit in the table above, which only limits how many records a merchant can choose in one dialog. For more than 200 IDs, split them across calls, or resolve only the IDs your current screen shows. Items keep the order of ids. IDs that no longer exist, or that the merchant can no longer see, are left out, so match items by id.

app.js javascript
if (bridge.supports("resources.resolve")) {
  const ids = savedProductIds.map((id) => `product:${id}`);
  try {
    const { items } = await bridge.resources.resolve({ resource: "product", ids });
    const titles = new Map(items.map((item) => [item.productId, item.title]));
    renderRows(titles);
  } catch (error) {
    // Labels are optional: keep showing the saved IDs.
    renderRows(new Map());
  }
}

Choose Media and Site Assets#

bridge.mediaPicker.open(options) selects files from one library. library is media by default, or site_asset. selectionMode is single by default, or multiple for up to 200 files. mediaType accepts image, video, document, 3d, or all (the default). A specific type restricts the selection. query sets the initial filename search (up to 200 characters). initialSelectionIds preselects files from the chosen library and type; single mode accepts at most one ID. Missing or deleted saved IDs are omitted. The Media and Site Assets pickers offer 20, 50, 100, or 200 files per page; changing the page size or search restarts at page 1.

javascript
const bridge = TringifyBridge.init();
await bridge.ready();

if (bridge.supports("resourcePicker.open") &&
    bridge.resourcePicker.RESOURCES.includes("media")) {
  const { selection } = await bridge.mediaPicker.open({
    library: "media", // Use "site_asset" for Site Assets.
    selectionMode: "multiple",
    mediaType: "image",
    initialSelectionIds: [],
  });
  // Save file IDs for later use. Cancellation returns an empty array.
  const fileIds = selection.map(file => file.id);
}

Each selected file includes id, title (the filename), url, fileType (upload or site_asset), mediaType, and mimeType. subtitle, previewUrl, and altText are included when available. Store the IDs when saving your app settings. Reopen with those IDs to preselect the files, or use bridge.resources.resolve({ resource: "media", ids }) (or site_asset) to retrieve their current metadata. Access is checked again before a selection is returned.

Edit a message template#

bridge.templateEditor.open({ kind, type }) opens the host message editor for kind: "sms" or kind: "whatsapp" and resolves with { saved, template? }. It edits one existing message type in the current store. Opening requires read_marketing; saving also requires write_marketing, subject to the merchant’s permissions. A read-only session can view the content. type is required and accepts up to 100 characters. After ready(), call bridge.templateEditor.supports(kind) to check whether the current host can edit that channel. Email and provider-owned template selection are outside this editor.

app.js javascript
await bridge.ready();
if (bridge.templateEditor.supports("whatsapp")) {
  const result = await bridge.templateEditor.open({
    kind: "whatsapp",
    type: "order_confirmation",
  });
  if (result.saved) refreshPreview();
}

Grant picker access to an installed private app#

A private app’s saved permissions are its draft configuration. Existing installations keep the permissions previously approved by the merchant. This also applies to apps installed from a draft install link. Media selection requires read_files; Site Assets selection requires read_site_assets.

In Developer Portal, open your app, save the required permissions in Settings, then open Private updates. Select the installed stores, review the access changes, optionally describe what changed, and select Send update. This freezes your saved app configuration for those stores. Private updates do not require marketplace review. Further draft edits do not change an update already sent.

The merchant receives an app update notification according to their notification preferences. They can open Apps, choose Review update, inspect the requested access, and select Accept and update. The installation and its data stay in place. When granted scopes change, Tringify rotates the app’s Store API token and delivers the token-rotation lifecycle event. Handle that event on your app server, then reopen the embedded app to establish a new App Bridge session.

Picker coverage and boundaries#

There are 13 built-in resource types on current StoreAdmin, iOS, and Android hosts. The supported-resources table above lists their app scopes, query limits, and filters. The merchant must also have permission to read those records. Check bridge.supports("resourcePicker.open") and bridge.resourcePicker.RESOURCES.includes(resource) after bridge.ready() before showing an action.

Use case

Built-in selection

Catalog

Products (including variant drilldown), collections, categories, brands, vendors, product attributes, and product tags; up to 50.

Customers and orders

Existing customers or orders; up to 50.

Locations

Existing store or app-managed locations; up to 20.

Inventory groups

Existing inventory groups; up to 50.

Metafield definitions

Eligible definitions for the supported owner type; up to 100.

Media and Site Assets

Existing files in one library; up to 200. Use mediaPicker.open() for the library/type convenience options.

There is no standalone variant picker: open a variable product to choose its variants. Product tags do not select customer tags. There are no built-in host pickers for markets, currencies, discounts, shipping methods, payment providers, staff, domains, pages, blog posts, menu items, or customer addresses. Build app-owned controls for other values, using public APIs where available and permitted. The UI Kit’s enhanceSelect can display your own local choices; it does not add a host resource type.

javascript
let savedTags = []; // Restore your app's saved tags here.
async function chooseTags() {
  await bridge.ready();
  if (!bridge.supports("resourcePicker.open") ||
      !bridge.resourcePicker.RESOURCES.includes("tag")) return;
  const { selection } = await bridge.resourcePicker.open({
    resource: "tag",
    selectionMode: "multiple",
    initialSelectionIds: savedTags.map(tag => tag.id),
  });
  if (selection.length === 0) return; // Cancel: keep the previous selection.
  savedTags = selection;
  // Render these choices and mark your app settings as unsaved.
}
function clearTags() {
  savedTags = [];
  // Render the empty selection and mark your app settings as unsaved.
}

Message editor behavior and boundaries#

SMS and WhatsApp have independent saved bodies. The editor inserts complete message values at the cursor, shows a preview, and keeps the draft after a failed save so the merchant can retry. Closing an edited draft asks whether to discard it. Cancellation returns { saved: false }; refresh app-owned previews only after a successful save. Saving content does not send a message or change a provider mapping. A provider may require its own approved template and mapping before delivery.

Only one hosted dialog can be open at a time. Wait for a dialog, picker, or template editor to finish before opening another; overlapping requests return DIALOG_ALREADY_OPEN. Context scopes reflect the app’s grants and the current merchant’s permissions. Native navigation accepts supported destinations within the current store; unavailable destinations return an error. Save-bar changes require confirmation before leaving the app.