Orbit Commerce
Plugin guides

Getting started

Build, embed, and ship your first Orbit plugin — an external web app that runs inside the Orbit store dashboard and talks to the Orbit API with scoped OAuth tokens.

This guide walks you from an empty directory to a plugin page rendering inside the dashboard iframe. By the end you will have a Next.js app, the SDK installed, a manifest, an embed page that receives a live store context, and the headers required for iframe embedding.

What an Orbit plugin is

An Orbit plugin is an external, standalone web app that you build and host yourself (Next.js, React, or any stack that serves HTTPS). Orbit does not run your code. Instead, the store dashboard embeds your app as an <iframe> and brokers a secure connection to it.

Three properties define a plugin:

  • Embedded, not installed into Orbit. Your UI mounts inside the dashboard through an iframe. Communication between the dashboard and your app happens over window.postMessage, which the SDK wraps for you.
  • OAuth-scoped. Your plugin requests a set of scopes in its manifest. The merchant consents to them at install time, and every API call your plugin makes is checked against the granted scopes.
  • Owns its own data. Your plugin stores everything in its own database. It cannot read or write Orbit's database directly — all Orbit data flows through the public API.

A single plugin is installed on many stores. Each store grants its own consent and carries its own context, so you never hard-code a store identity.

Architecture overview

There are two communication paths to understand.

                postMessage (token + storeId)
  ┌─────────────────────────┐   <iframe>   ┌──────────────────────────┐
  │  Orbit store dashboard  │ ───────────▶ │  Your plugin app (HTTPS) │
  │admin.myorbitcommerce.net│ ◀─────────── │  Next.js / React / ...   │
  └─────────────────────────┘  UI helpers  └────────────┬─────────────┘
                                                         │ Bearer token + x-store-id
                                                         ▼
                                            ┌──────────────────────────┐
                                            │  Orbit API               │
                                            │  api.myorbitcommerce.net  │
                                            │  /v1/*  /graphql  /oauth/*│
                                            └──────────────────────────┘
  • Dashboard ⇄ your app (postMessage). When the dashboard loads your iframe, it sends the current session token and storeId over postMessage. The SDK listens for these. Your app can also post UI requests back — toasts, navigation, modals, resize — through the same channel.
  • Your app → Orbit API. With the token and store context in hand, the SDK calls the Orbit API on https://api.myorbitcommerce.net, attaching Authorization: Bearer <token> and x-store-id: <storeId> to each request. The public surfaces are REST at /v1/*, GraphQL at /graphql, and the OAuth token endpoints at /oauth/*.

The store and token are provided dynamically by the dashboard at runtime. Never bake a storeId into an environment variable or constant — the same deployed plugin serves every store that installs it.

Quick start

1. Create a Next.js app

npx create-next-app@latest my-orbit-plugin --typescript
cd my-orbit-plugin

2. Install the SDK

npm install @orbitcommerce/sdk

The SDK exposes the OrbitClient class, which handles the postMessage handshake, token refresh, and typed API calls.

3. Add a plugin.json manifest

The manifest declares your plugin's identity, where its UI mounts, and which scopes it needs. You will import this in the partner dashboard wizard when you submit (see Publishing).

Scopes follow a strict <resource>:<action> convention where the resource is singular and kebab-case — product:read, order:read, not products:read. Request only what you need; the merchant sees and consents to this exact list.

{
  "name": "Order Sync",
  "pluginId": "order-sync",
  "shortDescription": "Sync orders to your fulfillment system",
  "description": "Order Sync mirrors new and updated orders from your Orbit store into an external fulfillment system, keeping statuses in step without manual data entry.",
  "version": "1.0.0",
  "category": "sales",
  "tags": ["orders", "fulfillment"],
  "extensionPoints": [
    {
      "target": "dashboard.menu",
      "url": "https://my-orbit-plugin.example.com/embed",
      "label": "Order Sync"
    }
  ],
  "oauth": {
    "scopes": ["product:read", "order:read", "order:list"]
  },
  "ui": {
    "menu": { "label": "Order Sync" }
  }
}

See the full list of available scopes in the Scope Catalog, and the Scopes guide for how requesting and enforcement work.

4. Build the embed page

Create the page that the extensionPoints[].url points to. It constructs an OrbitClient, waits for the dashboard handshake with ready(), then reads the live store context.

// app/embed/page.tsx
'use client';

import { useEffect, useState } from 'react';
import { OrbitClient } from '@orbitcommerce/sdk';

export default function EmbedPage() {
  const [storeId, setStoreId] = useState<string | null>(null);
  const [error, setError] = useState<string | null>(null);

  useEffect(() => {
    const orbit = new OrbitClient();

    orbit
      .ready() // resolves once the token + storeId arrive via postMessage
      .then(() => {
        setStoreId(orbit.getStoreId());
      })
      .catch((e: unknown) => {
        setError(e instanceof Error ? e.message : 'Failed to initialise');
      });

    // remove the postMessage listener on unmount
    return () => orbit.destroy();
  }, []);

  if (error) return <p>{error}</p>;
  if (!storeId) return <p>Connecting to Orbit…</p>;

  return <p>Connected to store {storeId}</p>;
}

A few things to note:

  • new OrbitClient() with no arguments is the correct iframe constructor. The token, storeId, and API URL all arrive over postMessage — you do not pass them in.
  • ready() accepts an optional timeout in milliseconds (default 10000). It rejects if the handshake does not complete in time, so always handle the rejection.
  • Read context only after ready() resolves: orbit.getStoreId() and orbit.getToken(). Calling them before the handshake returns nothing useful. Your granted scopes live in the token claims — see Scopes.
  • Call orbit.destroy() when the component unmounts to detach the listener.

Once connected, you can make API calls — for example orbit.query(...) / orbit.mutate(...) for GraphQL, or typed REST clients like orbit.orders and orbit.products. See the SDK guide for the full surface.

5. Configure CSP and HTTPS for iframe embedding

The dashboard can only embed your app if it is served over HTTPS and sends a Content-Security-Policy header whose frame-ancestors directive allows Orbit's domains. Without this header, the browser refuses to render your page inside the dashboard iframe.

Add the header in next.config.ts:

// next.config.ts
import type { NextConfig } from 'next';

const FRAME_ANCESTORS =
  "frame-ancestors 'self' https://*.orbitcommerce.net https://*.myorbitcommerce.net http://localhost:*";

const nextConfig: NextConfig = {
  async headers() {
    return [
      {
        source: '/embed/:path*',
        headers: [
          {
            key: 'Content-Security-Policy',
            value: FRAME_ANCESTORS,
          },
        ],
      },
    ];
  },
};

export default nextConfig;

The http://localhost:* entry lets you test against a locally running dashboard during development. In production your plugin must be reachable over HTTPS — browsers will not embed an insecure origin alongside the dashboard's secure one.

6. Run and embed

Start your app, expose it over HTTPS (for local development, a tunnel such as an HTTPS dev proxy works), and point your manifest's extensionPoints[].url at the /embed route. When the dashboard loads the iframe, the handshake fires, ready() resolves, and you will see the connected store ID render.

Next steps

  • Authentication — session vs access tokens, the postMessage handshake, and refreshing tokens for backend or background work.
  • SDK reference — the full OrbitClient surface: GraphQL queries, typed REST clients, billing, and the iframe UI helpers.
  • Scopes — how to choose scopes, the singular naming convention, consent, and 401/403 enforcement. Browse the complete Scope Catalog.
  • Webhooks — subscribe to topics like order.created, verify delivery signatures, and handle retries.
  • Publishing — the 4-step submission wizard, the review process, and getting listed on the marketplace.
  • API reference — the complete REST and GraphQL surface.