Apps
Build an app
From an empty folder to your app running in the merchant's desktop.
This is the path for the WhatsApp, Ads Manager and Finder developers, and for any app after them. Your app is a normal web app on your own HTTPS domain, in any stack. The desktop opens it in an iframe and App Bridge connects the two.
- 1
Tell us your app and its URL
Send to [email protected]: the app (WhatsApp, Ads Manager, Finder or a new one), the HTTPS URL where it runs (for example
https://whatsapp.yourdomain.com) and the scopes it needs. We register it: the desktop then frames that URL, lets it call the Admin API, and gives you a test shop.Already registered with their scopes:
whatsapp(read/write orders, read customers),ads-manager(read products, read orders),finder(read/write products). Ask if you need more. - 2
Let the desktop frame your app
Your pages must allow being framed by the desktop, and only by it. Send this header on every page:
Content-Security-Policy: frame-ancestors https://ostorya.comRemove any
X-Frame-Options: DENYorSAMEORIGINheader (they block the iframe). - 3
Load App Bridge
Load it from Ostorya (no install), as a module or a classic script:
import { createAppBridge } from "https://dev.ostorya.com/app-bridge/v1.mjs"; const bridge = createAppBridge("https://ostorya.com", { theme: (t) => document.documentElement.dataset.theme = t.appearance, });Create the bridge once when your app starts. It tells the desktop the app is ready and receives the theme and a token. With a bundler you can also copy the file into your project; it has no dependencies.
- 4
Call the Admin API
const { shop, orders } = await bridge.graphql(` query { shop { name currencyCode } orders(first: 20, query: "status:open") { nodes { id name createdAt totalPriceSet { shopMoney { amount currencyCode } } customer { displayName phone } } } } `);bridge.graphql()adds the token, refreshes it when needed and throws with the error messages when the response haserrors. Browse every query and mutation in the API reference. - 5
Feel like part of the desktop
Set the window title, report your page so a refresh reopens it, and use desktop toasts:
bridge.setTitle("Conversations"); bridge.setLocation(location.pathname + location.search); bridge.notify("Message sent"); bridge.notifyError("Couldn't reach WhatsApp");Follow the App design guidelines: transparent background, glass cards, the merchant's accent color, light/dark, and right-to-left for Arabic.
- 6
Test in the desktop
Sign in to ostorya.com with your test shop and click your app in the dock. Outside the desktop
bridge.embeddedisfalseand token requests fail with "Open this app from the Ostorya desktop.": show that message instead of a broken page.Developing on your laptop
The desktop only frames registered HTTPS origins, so
localhostcan't be framed. Use a stable tunnel (Cloudflare Tunnel, ngrok with a reserved domain) and register that URL as your development origin, next to production.
A complete minimal app#
One HTML file, served with the frame-ancestors header above:
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<style>
body { margin: 0; background: transparent; font: 15px system-ui; color: #111113; }
[data-theme="dark"] body { color: #E7EEE8; }
.card { margin: 16px; padding: 20px; border-radius: 22px; background: rgb(255 255 255 / .6); backdrop-filter: blur(24px); }
[data-theme="dark"] .card { background: rgb(20 30 25 / .55); }
</style>
</head>
<body>
<div class="card"><h1 id="shop">Loading…</h1><ul id="orders"></ul></div>
<script type="module">
import { createAppBridge } from "https://dev.ostorya.com/app-bridge/v1.mjs";
const bridge = createAppBridge("https://ostorya.com", {
theme: (t) => { document.documentElement.dataset.theme = t.appearance; document.documentElement.dir = t.locale === "ar" ? "rtl" : "ltr"; },
});
bridge.setTitle("Hello Ostorya");
try {
const data = await bridge.graphql(`{ shop { name } orders(first: 5) { nodes { name totalPriceSet { shopMoney { amount currencyCode } } } } }`);
document.getElementById("shop").textContent = data.shop.name;
document.getElementById("orders").innerHTML = data.orders.nodes.map((o) => `<li>${o.name} · ${o.totalPriceSet.shopMoney.amount} ${o.totalPriceSet.shopMoney.currencyCode}</li>`).join("");
} catch (e) {
bridge.notifyError(e.message);
}
</script>
</body>
</html>