App Bridge gives you a bunch of APIs that actually work most of the time. But like everything in web development, there are sharp edges that'll cut you if you're not careful.
Resource Picker - Works Great Until It Doesn't

The Resource Picker is probably the most useful thing in App Bridge. It gives merchants the familiar product/collection browser from the admin.
What works: Multi-selection, filtering, pre-selected items. Returns proper GraphQL objects with all the data you need.
What doesn't: The picker breaks randomly in Safari if you don't initialize App Bridge properly first. Also, variant selection randomly breaks - edit a product selection and suddenly variants disappear. No fix, just deal with it.
// This will fail silently if App Bridge isn't loaded yet
shopify.resourcePicker({
type: 'product',
action: 'select',
multiple: true
});
// Better approach - always check this shit first
if (window.shopify && shopify.resourcePicker) {
try {
const products = await shopify.resourcePicker({
type: 'product',
action: 'select',
multiple: true
});
// Don't trust the variants array - it's often broken in Safari
console.log('Selected products:', products);
} catch (error) {
console.error('ResourcePicker failed (probably Safari):', error);
// Fallback to manual product selection or show error message
}
}
Modal Dialogs That Randomly Disappear
Modal dialogs work fine in Chrome but have weird behavior in mobile Safari. The modal will sometimes close when the user scrolls, even though they didn't tap outside.
The legacy ui-modal component works more reliably but limits what you can put inside - no complex forms or file uploads.
// Watch out - this breaks on mobile Safari randomly
const modal = shopify.modal.show({
title: 'Product Details',
content: '<div>Complex content here</div>'
});
Pro tip: Use the src modal approach for anything complex. Render your form in a separate route and point the modal there.
Authentication - Session Tokens vs OAuth Hell
Session token authentication is way better than the old OAuth approach, but it has quirks:
The good: Works in Safari, handles cookie restrictions, automatic token refresh.
The bad: Tokens expire randomly and there's no good way to handle refresh failures. Your app just... stops working until the user refreshes the page.
Production nightmare: Session tokens break if the user has multiple Shopify tabs open. One tab logs out, all tabs fail.
Direct API Access - The Gotcha That Cost Me 3 Hours

The direct GraphQL API access works great until it doesn't:
// This will fail silently if you fuck up the app.toml config
const res = await fetch('shopify:admin/api/2024-07/graphql.json', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
query: `query GetProduct($id: ID!) {
product(id: $id) {
title
variants(first: 10) {
edges {
node {
id
price
}
}
}
}
}`,
variables: {id: 'gid://shopify/Product/1234567890'}
})
});
// CRITICAL: Always check for GraphQL errors or you'll debug for hours
const data = await res.json();
if (data.errors) {
console.error('GraphQL errors that will ruin your day:', data.errors);
// Don't ignore these or prepare for debugging hell
return; // Actually handle this shit properly in your code
}
// Also check if the response is totally empty (happens with wrong API version)
if (!data.data || !data.data.product) {
console.error('Empty response - check your app.toml direct_api config');
// Why is this not documented anywhere obvious?
}
Critical gotcha: You need this random setting in app.toml or everything breaks silently:
[app_access]
admin = { direct_api = true } # Missing this ruins your day
No error message, no warning, just API calls that pretend to work but return nothing. Found this buried on page 47 of the docs after debugging for half a day.

Works across desktop, mobile, and POS with the usual mobile web bullshit:
- iOS: File upload broken in modals (because Apple)
- Android: Toast notifications vanish randomly
- POS: Resource picker neutered to products only
Every Shopify update potentially breaks something. Keep your App Bridge version pinned or prepare for random production failures.
Debugging - The shopify
Global Variable
Access the global shopify
object in browser console to see what's broken:
// Check if App Bridge is loaded
console.log(window.shopify);
// See current authentication state
console.log(shopify.idToken());
// Debug modal state
console.log(shopify.modal.data);
The Web Vitals integration is nice for performance monitoring, but adds extra network calls that show up in dev tools as noise.
Integration with Polaris - Mostly Smooth
Polaris components like Page, Modal, Loading, Toast, and ResourcePicker delegate to App Bridge automatically. This works great until you need custom behavior - then you're stuck with whatever App Bridge provides.
Real talk: Toast integration breaks every few months with Polaris updates. Pin everything in production or wake up to angry users. This saves you weeks of auth debugging, which is worth way more than the learning curve.