mcpField
S
salla-embedded-app

salla-embedded-app

github.com/sallaappDevelopment282 installsUpdated 2w ago
Repository

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

  1. 01Register and manage embedded pages
  2. 02Initialize the Salla embedded SDK
  3. 03Verify tokens through a backend session flow
  4. 04Sync dashboard theme and locale
  5. 05Wire navigation, UI, and checkout modules

Use cases

  1. 01Add a custom page to a Salla merchant dashboard
  2. 02Authenticate an embedded app with backend-verified tokens
  3. 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-app

After 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.md
design-guidelines.md
implementation-guide.md
sdk-modules-guide.md

Details

From the project README

Salla 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, header S-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_id introspection 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

ToolActionWhat it does
salla_embedded_pageslist / create / update / deleteManage 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:

  1. What does this page do? (settings UI, analytics dashboard, addon purchase, etc.)
  2. Which SDK modules will you need?
    • auth — getToken, refresh, introspect (dev-only), plus core init/ready/destroy
    • page — setTitle, navigate, redirect, navTo
    • nav — action buttons (setAction/onActionClick/clearAction) and sub-nav items
    • ui — toasts, confirm dialogs, loading, breadcrumbs
    • checkout — addon purchase
  3. 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 as https://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 appends token, 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):

ImageWhere setDimensions / limitShows
Embedded App Banner (embedded_image)app_publish features sectionmin 710×260 px (recommended 1420×520), max 512 KBthe app's embedded/iframe presentation
Salla promotional imageapp_publish features section (promotional/featured)confirm dimensions via app_publish readiness / salla-docs before generatingSalla'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:

  1. Embeddability — your host must let Salla frame the page (set Content-Security-Policy: frame-ancestors https://s.salla.sa, drop X-Frame-Options), or the dashboard shows a blank/"refused" pane.
  2. 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_url at 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:

  1. await embedded.init() — establish the bridge; read layout.
  2. embedded.auth.getToken() — retrieve the short-lived token from the iframe URL (returns string | null; handle null = opened outside Salla).
  3. Send the token to YOUR backend. The frontend is a courier — do not make authz decisions here.
  4. Backend verifies via POST https://api.salla.dev/exchange-authority/v1/introspect, header S-Source: <YOUR_APP_ID>, body { "token": "..." } → success nests the claims under data: read data.merchant_id / data.user_id / data.exp (data.exp is an ISO-8601 datetime string, not a Unix timestamp). Backend mints its own session (JWT / secure cookie).
  5. embedded.ready() — call only after the backend confirms and your data is loaded. The dashboard shows a loading overlay until you do.
  6. 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-app only 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

TopicLink
Overview (hub)https://docs.salla.dev/embedded-sdk/overview.md
Getting Startedhttps://docs.salla.dev/embedded-sdk/getting-started.md
Installationhttps://docs.salla.dev/embedded-sdk/installation.md
Create an embedded apphttps://docs.salla.dev/embedded-sdk/create-app.md
Authenticationhttps://docs.salla.dev/embedded-sdk/authentication.md
Token Introspect (API)https://docs.salla.dev/27474794e0.md
App Design Guidelineshttps://docs.salla.dev/embedded-sdk/design-guidelines.md
Playground / testinghttps://docs.salla.dev/embedded-sdk/playground.md
Support & communityhttps://docs.salla.dev/embedded-sdk/resources/support.md
Implementation guidereferences/implementation-guide.md
SDK module methodsreferences/sdk-modules-guide.md

Per-module pages live under https://docs.salla.dev/embedded-sdk/modules/... (auth, page, nav, ui, checkout). The bundled sdk-modules-guide.md mirrors their signatures.

Comments

Markdown supported