About salla-embedded-app
salla-embedded-app is a skill for integrating custom pages into the Salla Merchant Dashboard. It covers page registration, SDK setup, secure backend token verification, and wiring dashboard modules such as navigation and UI controls.
Key features
- 01Register and manage embedded pages
- 02Initialize the Salla embedded SDK
- 03Verify tokens through a backend session flow
- 04Sync dashboard theme and locale
- 05Wire navigation, UI, and checkout modules
Use cases
- 01Add a custom page to a Salla merchant dashboard
- 02Authenticate an embedded app with backend-verified tokens
- 03Connect page navigation and dashboard controls
MCP support
It uses the Salla Partners MCP server to register and manage embedded dashboard pages through the `salla_embedded_pages` tool. The MCP server must be connected for this step.
Install
Copy the command and run it in a terminal.
npx skills add https:/ / github.com/ sallaapp/ salla-partners-agent-kit --skill salla-embedded-appAfter installing, type/salla-embedded-appin Claude Code to confirm it loaded.
Bundled files
References and scripts installed along with SKILL.md.
references/auth-and-session.mddesign-guidelines.mdimplementation-guide.mdsdk-modules-guide.mdDetails
From the project READMESalla Embedded App Flow
Integrate a custom page inside the Salla Merchant Dashboard. Step 1 performs the page registration with the Salla Partners MCP; the SDK steps are code you write into the app. Follow the steps in order — complete each gate before moving to the next.
The official Salla model is Trust-but-Verify: Salla passes a short-lived token in the
iframe URL; your frontend captures it with embedded.auth.getToken() and hands it to your
backend, which verifies it with Salla's Introspection API and then mints its own session.
The frontend is a courier — it never makes authorization decisions. Step 3 is the recipe;
references/auth-and-session.md is the authoritative source.
Security rules (binding)
- Authenticate every page on the backend. The backend verifies the Salla token (introspect), mints its own session, and authorizes each request against that session — the frontend only ferries the token.
- Verify via the backend introspect
(
POST https://api.salla.dev/exchange-authority/v1/introspect, headerS-Source: <YOUR_APP_ID>).embedded.auth.introspect()(Client Introspect) is dev/debug only — the docs say it "should not be used as a primary authentication method." Full request/response → Step 3 +auth-and-session.md. - Identity comes only from introspection. Scope every route and query to the
merchant_idintrospection returned; reach the dashboard exclusively through Salla's native embedded-app support (no standalone/dashboard?store_id=…URL that trusts a query param or referer).
Tools
| Tool | Action | What it does |
|---|---|---|
salla_embedded_pages | list / create / update / delete | Manage the app's embedded (iframe) dashboard pages |
Prerequisite: the Salla Partners MCP server must be connected, and you need the app's
app_id. If a tool returns "Salla session expired", re-run the login flow.
Step 0 — Discover
Ask before starting:
- What does this page do? (settings UI, analytics dashboard, addon purchase, etc.)
- Which SDK modules will you need?
auth—getToken,refresh,introspect(dev-only), plus coreinit/ready/destroypage—setTitle,navigate,redirect,navTonav— action buttons (setAction/onActionClick/clearAction) and sub-nav itemsui— toasts, confirm dialogs, loading, breadcrumbscheckout— addon purchase
- Does the page need to sell addons? (activates the Checkout module)
Step 1 — Register the Page
Register the iframe page by calling salla_embedded_pages with action: "create",
the app_id, and:
route— kebab path segment for the page (≥ 3 chars). Appears ashttps://s.salla.sa/embedded/app/{appId}/{route}.iframe_url— the full URL Salla loads in the iframe (e.g.https://dashboard.myapp.net/salla/embedded). Salla appendstoken,theme,lang.default— optional; mark this as the app's default landing page.
create returns the new page; update returns no body from the Portal, so the tool echoes
the fields you changed ({ page: { id, route, iframe_url, default, updated: true } }) as
confirmation. Call salla_embedded_pages action=list for the authoritative current state. Use
update / delete to change or remove a page.
Manual fallback: Portal → App Details → Embedded Pages → Add page.
Gate: "salla_embedded_pages action=list returns the page — can you see it in the
merchant dashboard sidebar?"
Embedded app images (set at publish)
An embeddable app carries two App-Store images, both written through the publication, not the SDK. Set them while publishing (Step 6 → salla-publication-consistency):
| Image | Where set | Dimensions / limit | Shows |
|---|---|---|---|
Embedded App Banner (embedded_image) | app_publish features section | min 710×260 px (recommended 1420×520), max 512 KB | the app's embedded/iframe presentation |
| Salla promotional image | app_publish features section (promotional/featured) | confirm dimensions via app_publish readiness / salla-docs before generating | Salla's promotional / featured (build) section |
The Salla promotional image is a second image (in addition to the Embedded App Banner) used in Salla's promotional/featured section — supply it whenever the app is featured there.
Image-generation enrichment applies to both: when either is missing (or it's the first
publication) and an image-generation tool is available → confirm the dimensions above, generate,
salla_upload, then set via app_publish features section; otherwise ask the merchant for a real
asset. Don't duplicate the steps — the canonical recipe (and every other listing image field)
lives in
salla-app-ui-builder.
Manual fallback: My Apps → App → App Details → Start publishing → App Features.
Step 2 — Install & Initialize the SDK
Install the npm package — do NOT rely on a CDN global. Import embedded from the
package so TypeScript validates the method names; the CDN build's global name/shape can
differ and silently break boot.
npm install @salla.sa/embedded-sdk
import { embedded } from "@salla.sa/embedded-sdk";
CDN fallback (vanilla / non-bundled pages only — confirm the global first):
<script src="https://unpkg.com/@salla.sa/embedded-sdk/dist/umd/index.js"></script>
<script>
const embedded = Salla.embedded; // or SallaEmbeddedSDK.embedded
</script>
Initialize on every page load — init() establishes the postMessage bridge and resolves
with the dashboard layout:
const { layout } = await embedded.init({ debug: false });
// layout carries theme ("light"|"dark"), locale ("ar"|"en"), dir ("rtl"|"ltr"), …
Two things block a first embedded app even when the SDK calls are correct:
- Embeddability — your host must let Salla frame the page (set
Content-Security-Policy: frame-ancestors https://s.salla.sa, dropX-Frame-Options), or the dashboard shows a blank/"refused" pane. - Dev loop — the page needs the dashboard handshake for a token, so you can't open it in a
plain tab. Tunnel localhost, point
iframe_urlat the tunnel, install on a demo store, Run App.
Headers, dev loop, framework gotchas (React/Next, Vue), a full worked example, and a copy-paste
starter → references/implementation-guide.md
Module guide → references/sdk-modules-guide.md
Step 3 — Authenticate the Session (Trust-but-Verify)
The flow, in order:
await embedded.init()— establish the bridge; readlayout.embedded.auth.getToken()— retrieve the short-lived token from the iframe URL (returnsstring | null; handlenull= opened outside Salla).- Send the token to YOUR backend. The frontend is a courier — do not make authz decisions here.
- Backend verifies via
POST https://api.salla.dev/exchange-authority/v1/introspect, headerS-Source: <YOUR_APP_ID>, body{ "token": "..." }→ success nests the claims underdata: readdata.merchant_id/data.user_id/data.exp(data.expis an ISO-8601 datetime string, not a Unix timestamp). Backend mints its own session (JWT / secure cookie). embedded.ready()— call only after the backend confirms and your data is loaded. The dashboard shows a loading overlay until you do.- On failure — call
embedded.destroy()to exit gracefully rather than leaving the merchant on a hung loading screen.
// 1. Bridge + layout
const { layout } = await embedded.init({ debug: false });
try {
// 2. Capture the short-lived token from the URL
const token = embedded.auth.getToken();
if (!token) throw new Error("Opened outside Salla — no token");
// 3 + 4. Hand the token to YOUR backend; it introspects and mints a session.
const res = await fetch("/api/auth/session", {
method: "POST",
headers: { "Content-Type": "application/json" },
credentials: "include",
body: JSON.stringify({ token }),
});
if (!res.ok) throw new Error("Verification failed");
// 5. Backend confirmed + data loaded → reveal the iframe.
await loadDashboardData();
embedded.ready();
} catch (err) {
console.error("Auth failed", err);
embedded.destroy(); // exit gracefully instead of hanging on the loading overlay
}
embedded.auth.introspect() is dev/debug ONLY — a frontend reference helper for the
introspection flow that the docs explicitly say "should not be used as a primary authentication
method." Production authorization is always the backend introspect above.
When the backend returns 401/expiry → call embedded.auth.refresh(); Salla reloads the iframe
with a fresh token, your bootstrap re-runs, and the flow restarts.
Full backend introspect (with S-Source), session minting, and the 401→refresh loop →
references/auth-and-session.md
Gate: "Does your backend introspect the token (with the S-Source header) and mint its own
session before you call ready()? Test with a demo store."
Step 4 — Sync Theme & Locale
Theme, locale, and direction come from the layout returned by embedded.init() — do not read
query params manually:
if (layout) {
document.documentElement.setAttribute("data-theme", layout.theme);
document.documentElement.setAttribute("lang", layout.locale);
document.documentElement.setAttribute("dir", layout.dir); // "rtl" for ar, "ltr" for en
}
layout is the source of truth for appearance; re-apply it whenever the SDK re-initializes (e.g.
after embedded.auth.refresh() reloads the iframe). The dashboard also supports dynamic theme
switching — listen with embedded.onThemeChange() where available.
Design tokens, brand colors, RTL patterns → references/design-guidelines.md
Step 5 — Wire SDK Modules
Drawing visible UI? Stop here and load → salla-embedded-ui. Its gates (native tokens, No-Chrome, RTL, live screenshot) are binding; return to
salla-embedded-apponly for SDK wiring, not styling.
Based on your needs from Step 0, implement the relevant modules:
Auth — refresh on recoverable expiry; destroy only when unrecoverable:
// Backend reported the token expired (401): refresh — Salla re-renders the iframe with a fresh one.
embedded.auth.refresh();
// Only when auth is unrecoverable (a fresh token still can't verify, app suspended):
embedded.destroy(); // tears down the iframe so the dashboard doesn't hang
Order matters: a transient expiry must refresh(), not destroy().
Page — title and dashboard navigation:
embedded.page.setTitle("My App");
embedded.page.navigate("/orders"); // internal SPA route (React Router)
embedded.page.redirect("https://docs.my-app.com/help"); // external / full reload
embedded.page.navTo("/orders"); // auto-picks navigate vs redirect
Nav — action button + sub-nav items:
embedded.nav.setAction({
title: "Save",
value: "save",
icon: "hgi hgi-stroke hgi-tick-02",
}); // icon optional (Hugeicons class)
const off = embedded.nav.onActionClick((value) => {
if (value === "save") saveChanges();
});
embedded.nav.clearAction(); // when the merchant leaves this view
UI — toasts, loading, confirm, breadcrumbs:
embedded.ui.toast.success("Saved!");
embedded.ui.toast.error("Something went wrong");
embedded.ui.loading.show();
embedded.ui.loading.hide();
const { confirmed } = await embedded.ui.confirm({
title: "Delete?",
message: "This cannot be undone.",
variant: "danger", // confirm() resolves to { confirmed }, not a bare boolean
});
embedded.ui.breadcrumbs.hide(); // or .show()
Checkout — in-app addon purchase (if applicable):
→ follow the salla-addon-purchase-embedded skill (the embedded/in-app purchase flow;
for addon pricing/entitlement mechanics see salla-addon-purchase)
Full method signatures → references/sdk-modules-guide.md
Gate: "Test each module you're using in the SDK Playground before going to production." → Playground / Test Kit
Step 6 — Publish
Embedded pages go live when the parent app is published. Follow the publishing checklist
in the publish step of salla-app-builder.
Resources
| Topic | Link |
|---|---|
| Overview (hub) | https://docs.salla.dev/embedded-sdk/overview.md |
| Getting Started | https://docs.salla.dev/embedded-sdk/getting-started.md |
| Installation | https://docs.salla.dev/embedded-sdk/installation.md |
| Create an embedded app | https://docs.salla.dev/embedded-sdk/create-app.md |
| Authentication | https://docs.salla.dev/embedded-sdk/authentication.md |
| Token Introspect (API) | https://docs.salla.dev/27474794e0.md |
| App Design Guidelines | https://docs.salla.dev/embedded-sdk/design-guidelines.md |
| Playground / testing | https://docs.salla.dev/embedded-sdk/playground.md |
| Support & community | https://docs.salla.dev/embedded-sdk/resources/support.md |
| Implementation guide | references/implementation-guide.md |
| SDK module methods | references/sdk-modules-guide.md |
Per-module pages live under
https://docs.salla.dev/embedded-sdk/modules/...(auth, page, nav, ui, checkout). The bundledsdk-modules-guide.mdmirrors their signatures.