Plugin SDK

plugin sdk

Pages and UI slots

Compose plugin routes and contribute to host interfaces.

Pages use the shared declarative page nodes. Define a page, then register its presentation separately:

import { definePage, PageHeader } from "@northgraindata/dsui-plugin-sdk";

const overview = definePage({
  path: "/",
  render: () => [PageHeader({ title: "My plugin" })],
});

// Inside setup(registry):
registry.page(overview, { id: "overview", title: "My plugin" });
registry.navigation({
  id: "overview-link",
  area: "primary",
  label: "My plugin",
  pageId: "overview",
});

definePage({ path, render }) supports static segments, :named parameters and one final *named wildcard. For example, /services/:serviceId/items/*path captures both serviceId and a nested path; the wildcard can be empty. The catalog includes declared paths. The browser and host share resolvePluginPage so legacy page IDs and static paths precede dynamic routes. A page render receives decoded route parameters. Use percent encoding for path segments and an optional URL fragment for client-side anchors.

Slots

Use defineSlot and registry.slot(slot) to contribute to a named host slot. The host owns the slot’s placement and supplies its context. See the example contributions.

sidebar.profile replaces the sidebar’s workspace profile when a plugin returns content. With no contribution, an empty result or a request error, the host keeps its default workspace/settings link. Request it with an empty serviceIds array; the result has serviceId: "" because this slot belongs to the workspace.

Its render input includes the authenticated principal and an undefined service. The host supplies the principal, never the request payload, and still checks plugin inspect permission. Service slots keep receiving their service and now also receive the authenticated principal. Keep personal account presentation and data lookup in the contributing plugin.

Overlay slots

An overlay is a plugin-owned slot rendered by the host above the current app screen. It is useful for help panels, guided tours and contextual actions. The plugin owns its nodes; the host owns positioning, the dialog frame and dismissal. Overlay IDs are local to their plugin. A trigger can open only a contribution from its own plugin:

import { Button, Card, Section, defineSlot } from "@northgraindata/dsui-plugin-sdk";

const helpOverlay = defineSlot({
  id: "help",
  slot: "overlay",
  presentation: { mode: "modal", label: "Help" },
  render: () => [
    Section({
      title: "Help",
      content: Card({ title: "Useful context", description: "Help content." }),
    }),
  ],
});

const sidebarHelp = defineSlot({
  id: "sidebar-help",
  slot: "sidebar.profile",
  render: () => [Button({ label: "Help", overlay: "help" })],
});

Use presentation: { mode: "drawer", width: 560 } for a right-side panel that fills the viewport height. The width defaults to 520 pixels and is limited to 320–900 pixels; on narrow screens the drawer uses the available viewport width. The host still handles Escape and focus restoration.

Use presentation: { mode: "anchored", target: "service.workspace.header" } to place a non-modal panel beside a stable host target. Add path: "/services/example/data" to navigate in the current tab before locating the target and highlight: true to dim the surrounding screen. The host waits for asynchronous pages, scrolls the target into view and keeps the panel inside the viewport. Targets are named by the host and exposed through data-dsui-overlay-target; plugins cannot pass CSS selectors or inject DOM. The current host publishes the service workspace header, navigation, content and individual page-link targets (service.workspace.page.data, for example). Dashboard targets include dashboard.services and dashboard.service.<serviceId>; app.content identifies the current main screen. These targets describe app surfaces rather than a particular plugin. If an anchored target is not present, the host displays a waiting state with a close control. Modal overlays open above the current screen and close with the host close control or Escape.

Use dock: "bottom-right" to keep the panel in a fixed corner while its highlight moves. Adapter and plugin pages can publish their own page-unique anchors with the Adapter SDK’s Target({ id: "team.runbook", content: ... }). The host also publishes query.editor, query.run, query.results, and dbt.model.source on the corresponding components. The Airflow graph publishes airflow.dag.graph; S3 object rows publish s3.object.<objectKey>.

Query pages in the PostgreSQL and DuckDB adapters accept ?sql=<encoded SQL> to fill the editor. Opening a deep link never runs the query automatically. An anchored contribution can advance after the visitor runs it successfully:

presentation: {
  mode: "anchored",
  path: "/services/warehouse/query?sql=SELECT%201",
  target: "query.editor",
  dock: "bottom-right",
  advanceOn: {
    serviceId: "warehouse",
    target: "query.results",
    event: "success",
    overlay: "query-result",
  },
}

advanceOn matches the interaction target, event and originating service. For plugin components use pluginId instead; omitting both defaults to the contributing plugin. Custom components report interactions through the optional client.notifyInteraction({ target, event }) hook. Events carry no action input or result data. Give the result contribution the same path to preserve editor state. Button({ action, successOverlay: "verified", ... }) opens a follow-up overlay only after its action succeeds; errors remain visible on the button.

Browser components

Built-in page primitives render through the host’s component registry. A custom browser component needs a declared browser entry (dsui.browser in the package manifest) or an explicit browserBundle for an installed plugin. The example plugin shows a browser entry with createComponents(React) and host-supplied React.