この記事は現在英語版のみです。翻訳は近日公開予定です。
Shopify App Bridge 4 Tutorial: Build Embedded Apps in 2026
App Bridge is the JavaScript library that connects an embedded Shopify app (running inside an iframe in the merchant admin) to the Shopify admin chrome — navigation, modals, toasts, resource pickers, contextual save bars. Version 4 (released 2024, default in 2026) is a complete rewrite: framework-agnostic, no React Provider, session tokens by default, no more CommonJS shim. If you started with App Bridge 2 or 3, the upgrade requires real changes. This tutorial walks through a fresh embedded app build using App Bridge 4 with React 19.
Key Takeaways
- App Bridge 4 ships as a single ES module loaded from Shopify's CDN — no npm install for the runtime
- Session tokens replace the old OAuth-redirect dance; backend exchanges them for offline access tokens
- APIs are now
shopify.<feature>globals (e.g.,shopify.toast.show,shopify.modal.show)- The
<App Provider>wrapper from App Bridge React is gone — APIs work without React context- Resource Picker, Contextual Save Bar, and Loading Bar are now web components (
<ui-resource-picker>etc.)- Polaris React 13+ is the recommended UI kit and ships compatible with App Bridge 4
What "embedded" means
An embedded Shopify app is a page in the merchant admin (https://admin.shopify.com/store/{shop}/apps/{app}) that loads your URL inside an iframe. The merchant never leaves Shopify; you get a sandboxed UI surface and a session token in every request. This is the mode App Store apps and most agency-built apps use today.
Non-embedded apps run on your own domain with full-page redirects. Shopify still supports them but they break the unified admin UX and are not eligible for App Store featured placement.
Project setup
Start from the official template:
npm create @shopify/app@latest -- --template node
You get a Remix backend, Polaris frontend, Prisma session storage, and App Bridge wired up. For our purposes we'll show the App Bridge moving parts in a Next.js app router build, since most of our customers are on Next.
<!-- app/layout.tsx — load App Bridge from CDN in <head> -->
<script
src="https://cdn.shopify.com/shopifycloud/app-bridge.js"
data-api-key={process.env.NEXT_PUBLIC_SHOPIFY_API_KEY}
></script>
That's the entire runtime. App Bridge attaches itself to window.shopify once the script loads. You access it as window.shopify or shopify (if the global is declared in TypeScript).
Session tokens for authenticated requests
The session token is a JWT signed with your client secret, valid for 60 seconds, that App Bridge mints on demand. You attach it to every request to your backend.
// Frontend
async function api(path: string, init?: RequestInit) {
const token = await shopify.idToken();
return fetch(`/api${path}`, {
...init,
headers: { ...init?.headers, Authorization: `Bearer ${token}` },
});
}
// Backend (Next.js route handler)
import { jwtVerify } from 'jose';
const secret = new TextEncoder().encode(process.env.SHOPIFY_API_SECRET!);
async function getShop(req: Request): Promise<string> {
const auth = req.headers.get('authorization')?.replace('Bearer ', '');
if (!auth) throw new Response('Unauthorized', { status: 401 });
const { payload } = await jwtVerify(auth, secret);
return new URL(payload.dest as string).hostname; // shop.myshopify.com
}
The token's dest claim tells you which shop made the request. Trust nothing else from the client.
Token exchange: getting an offline access token
The session token is enough to identify the shop, but not enough to call the Admin API. You exchange it for a long-lived offline access token via Shopify's token-exchange endpoint:
async function exchangeToken(shop: string, sessionToken: string) {
const res = await fetch(`https://${shop}/admin/oauth/access_token`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
client_id: process.env.SHOPIFY_API_KEY,
client_secret: process.env.SHOPIFY_API_SECRET,
grant_type: 'urn:ietf:params:oauth:grant-type:token-exchange',
subject_token: sessionToken,
subject_token_type: 'urn:ietf:params:oauth:token-type:id_token',
requested_token_type: 'urn:shopify:params:oauth:token-type:offline-access-token',
}),
});
const { access_token } = await res.json();
return access_token;
}
Cache the offline token in your database keyed by shop. It only expires when the merchant uninstalls the app.
Common UI APIs
Toasts
shopify.toast.show('Product saved', { duration: 3000 });
shopify.toast.show('Failed to save', { isError: true });
Modals
<ui-modal id="confirm-modal">
<s-paragraph>Are you sure you want to archive 142 products?</s-paragraph>
<ui-title-bar title="Confirm archive">
<button variant="primary" tone="critical" onClick="archive()">Archive</button>
<button onClick="document.getElementById('confirm-modal').hide()">Cancel</button>
</ui-title-bar>
</ui-modal>
document.getElementById('confirm-modal').show();
Resource Picker
const result = await shopify.resourcePicker({ type: 'product', multiple: true });
if (result) {
console.log(result.selection); // [{ id, title, handle, ... }]
}
Contextual Save Bar
The save bar pins to the bottom of the admin and is the canonical "you have unsaved changes" pattern.
<ui-save-bar id="save-bar">
<button variant="primary" onClick="save()">Save</button>
<button onClick="discard()">Discard</button>
</ui-save-bar>
Show it via document.getElementById('save-bar').show() when state diverges from saved.
Navigation
App Bridge owns the URL bar. Use its navigation API instead of router.push to keep the admin chrome in sync:
shopify.navigation.navigate('/products/123');
For external Shopify admin links (orders, customers):
shopify.navigation.navigate('shopify://admin/products/123');
Polaris UI
Polaris is Shopify's React component library matching the admin's visual language. Install Polaris React 13+:
npm install @shopify/polaris
import { AppProvider, Page, Card, Button } from '@shopify/polaris';
import enTranslations from '@shopify/polaris/locales/en.json';
export default function ProductsPage() {
return (
<AppProvider i18n={enTranslations}>
<Page title="Products" primaryAction={{ content: 'Add product' }}>
<Card>
<Button onClick={() => shopify.toast.show('Hello')}>Toast me</Button>
</Card>
</Page>
</AppProvider>
);
}
Webhooks for app lifecycle
Subscribe to these at install time. They are mandatory for App Store approval:
| Topic | Purpose |
|---|---|
app/uninstalled | Clean up shop's data, revoke access token |
customers/data_request | GDPR — surface customer data |
customers/redact | GDPR — delete customer data |
shop/redact | GDPR — delete shop data after 48h |
Verify HMAC on every webhook. We covered the full pattern in Shopify webhooks: HMAC, retries, idempotency.
App Bridge 3 vs 4 migration
| Feature | v3 | v4 |
|---|---|---|
| Install | @shopify/app-bridge npm | CDN script |
| React API | <Provider config={...}> | Globals via shopify.* |
| Session token | getSessionToken(app) | shopify.idToken() |
| Toast | Toast.create(app, opts) | shopify.toast.show() |
| Resource Picker | ResourcePicker.create() | shopify.resourcePicker() |
| Modal | Modal.create() | <ui-modal> web component |
| Loading | Loading.create() | shopify.loading(true) |
The migration is mostly mechanical but expect 4-8 hours for a non-trivial app.
Performance considerations
- App Bridge 4 adds ~12 KB gzipped to your initial load.
- The iframe boots in ~120 ms once the merchant clicks your app tile.
- Avoid synchronous calls to
shopify.idToken()in tight loops — token minting is rate-limited at ~1/sec per session. - Use
shopify.config.shopto detect the shop synchronously without a backend round trip.
Frequently Asked Questions
Do I need React to use App Bridge 4?
No. App Bridge 4 is framework-agnostic. The web components and shopify.* globals work in vanilla JS, Vue, Svelte, or React. Polaris React is React-only, but App Bridge isn't.
How do I test embedded apps locally?
Use shopify app dev from the CLI. It tunnels your local server through Cloudflare and registers the redirect URLs with Shopify. The CLI also handles partner-account auth so the embedded iframe can load against your dev shop.
Can I open my embedded app in a new tab?
The merchant can — Shopify exposes "Open in new tab" on every embedded app. When opened that way, App Bridge runs in standalone mode (no parent admin chrome). Your code should handle both cases. Use shopify.environment.embedded to check.
What about Shopify CLI vs raw build?
CLI accelerates scaffolding (Prisma, session storage, OAuth) but you can absolutely roll your own. We use raw Next.js + Drizzle for most agency builds because we need control over auth and data persistence. The CLI template is excellent for App Store apps where standard patterns are preferred.
How does this work with the new GraphQL Admin API?
App Bridge handles auth; once you have the offline access token, you call the Shopify Admin GraphQL API the same way as any other GraphQL client. The two systems are loosely coupled.
ECOSIRE builds embedded Shopify apps for Plus and standard merchants — billing, custom workflows, ERP connectors. Our Shopify app development team handles App Bridge migrations, App Store submissions, and ongoing maintenance.
執筆者
ECOSIRE TeamTechnical Writing
The ECOSIRE technical writing team covers Odoo ERP, Shopify eCommerce, AI agents, Power BI analytics, GoHighLevel automation, and enterprise software best practices. Our guides help businesses make informed technology decisions.
関連記事
Odoo フォーム ビューにカスタム ボタンを追加する方法 (2026)
Odoo 19 フォーム ビューにカスタム アクション ボタンを追加します: Python アクション メソッド、ビューの継承、条件付き可視性、確認ダイアログ。製造テスト済み。
Studio を使用せずに Odoo にカスタムフィールドを追加する方法 (2026)
Odoo 19 のカスタム モジュールを介してカスタム フィールドを追加します: モデル継承、ビュー拡張、計算フィールド、ストア/非ストアの決定。コードファースト、バージョン管理。
外部レイアウトを使用して Odoo にカスタム レポートを追加する方法
web.external_layout: QWeb テンプレート、paperformat、アクション バインディングを使用して、Odoo 19 でブランド化された PDF レポートを構築します。印刷ロゴ + フッターのオーバーライド付き。