Skip to content

SDK API Reference ​

Extension host code accesses Mantis through mantis. Extension panels access it through window.mantis. Every SDK method is asynchronous and returns a promise.

js
const manifest = await window.mantis.extension.getManifest();

If an API needs a permission and the extension did not declare it, the call rejects with an error.

Host and panel are not the same surface ​

The two runtimes share most of the SDK, but not all of it. The host is a Web Worker and the panel is a sandboxed iframe, and they are built separately, so a few members exist in only one of them. Writing host code against a panel example is the most common way to hit an undefined method.

MemberPanel (window.mantis)Host (mantis)
extension.getManifestYesYes
maps.list, maps.getActive, maps.getPointsYesYes
maps.openYesNo
clusters.listYesYes
selection.getYesYes
selection.setYes, but not installable (see below)No
bags.list, bags.createYesYes
panels.open, panels.closeYesYes
commands.execute, commands.registerCommandYesYes
backend.invokeYesYes
events.subscribeYesYes
workspaceState, globalState at the top levelYesNo, use context.workspaceState / context.globalState
rpcYesYes

Timeouts ​

Every RPC call on both bridges rejects after 30 seconds, with Mantis extension RPC timeout: <method> in panels and Mantis extension host RPC timeout: <method> in the worker. Command invocation has the same 30-second cap.

The Python backend's own default execution timeout is also 30 seconds, so the two ceilings coincide: a long analysis cannot be done in a single synchronous backend.invoke. Break the work into steps, or have the backend return a handle you poll.

Raw RPC ​

ts
window.mantis.rpc(method, payload?)

Low-level escape hatch used by the SDK helpers.

js
const manifest = await window.mantis.rpc('extension.getManifest');

Prefer the typed helpers below unless you are debugging the bridge.

Extension Host Context ​

Host main files export activate(context) and may export deactivate().

js
exports.activate = async function activate(context) {
  context.subscriptions.push(
    await mantis.commands.registerCommand('demo.my-ext.run', run),
  );
};

The context includes:

FieldDescription
extensionIdManifest extension id.
extensionUriVirtual URI for this installed extension.
manifestNormalized manifest.
apiVersionRequested SDK version.
subscriptionsDisposables cleaned up on host shutdown.
workspaceStateState scoped to the current space and extension.
globalStateState scoped to the extension across all spaces.

State API:

js
const count = await context.workspaceState.get('count', 0);
await context.workspaceState.update('count', count + 1);

In host code these are the only way to reach state. There is no top-level mantis.workspaceState.

Extension state is browser-local

Both scopes are written to localStorage in the Mantis page, under keys of the form mantis.extensionState.<spaceId|global>.<extensionId>.<key>. That means globalState is not account state: it does not follow the user to another browser or machine, it is shared with anyone using the same browser profile, and clearing site data destroys it. Do not store anything you cannot rebuild.

extension.getManifest() ​

Returns the normalized manifest for the installed extension.

js
const manifest = await window.mantis.extension.getManifest();
console.log(manifest.id, manifest.version);

No permission is required.

Panel storage (workspaceState / globalState) ​

Panels run in a tight iframe sandbox (allow-scripts only). They do not share the parent origin, so window.localStorage in the panel is isolated from Mantis and from other extensions.

Use the same APIs as the extension host context. Values are persisted in the main app via the SDK bridge (same keys as context.workspaceState / context.globalState in main).

js
const count = await window.mantis.workspaceState.get('count', 0);
await window.mantis.workspaceState.update('count', count + 1);

const theme = await window.mantis.globalState.get('theme', 'light');
await window.mantis.globalState.update('theme', 'dark');

Maps ​

Requires maps:read.

maps.list() ​

Returns known maps and the active map id.

js
const snapshot = await window.mantis.maps.list();

console.log(snapshot.maps);
console.log(snapshot.activeMapId);

maps.getActive() ​

Returns the active Mantis view metadata, or null when no map is active.

js
const active = await window.mantis.maps.getActive();

maps.getPoints(mapId?) ​

Returns points for a map. If mapId is omitted, Mantis uses the current map context.

js
const active = await window.mantis.maps.getActive();
const points = await window.mantis.maps.getPoints(active && active.mapId);

maps.open(mapId) ​

Requires maps:read. Panel only. The host worker does not expose this method.

Opens or focuses the main Mantis map panel for mapId.

js
await window.mantis.maps.open('map-id');

Clusters ​

Requires maps:read.

clusters.list(mapId?) ​

Returns cluster metadata for a map, including labels, colors, summaries, keywords, and point ids when available. Use this with maps.getPoints() because points store their cluster assignment as a cluster id.

js
const active = await window.mantis.maps.getActive();
const clusters = await window.mantis.clusters.list(active && active.mapId);
const clusterById = new Map(clusters.map((cluster) => [cluster.id, cluster]));

Selection ​

selection.get(mapId?) ​

Requires selection:read.

Returns a selection snapshot:

js
const selection = await window.mantis.selection.get();

console.log(selection.selected);
console.log(selection.currentSelection);
console.log(selection.selectedBags);

selection.set(pointIds, mapId?) ​

Not usable today

This method exists in the panel bridge and requires selection:write, but the server's install validator rejects that permission, so no installed extension can ever hold it. A manifest declaring selection:write fails to install entirely. The method is also absent from the host worker. Treat selection writing as unavailable until the backend allowlist changes.

When it becomes available, it selects points by id in the target map and moves the current selection to the first of them.

js
await window.mantis.selection.set(['point-a'], 'map-id');

Bags ​

bags.list(mapId?) ​

Requires selection:read.

Returns the current bags/global variables for a map.

js
const bags = await window.mantis.bags.list();

bags.create(name, pointIds, mapId?) ​

Requires bags:write.

Creates a bag from point ids.

js
await window.mantis.bags.create('Interesting Points', selectedPointIds);

Panels ​

panels.open(panel) ​

Requires panels:write.

Opens a Mantis panel by name. If panel matches a panel id or title contributed by the same extension, Mantis opens that extension panel. Otherwise Mantis tries to open a native Mantis panel.

js
await window.mantis.panels.open('Inspector');
js
// From this extension's host or panel code:
await mantis.panels.open('lifecyclePanel');

panels.close(panel) ​

Requires panels:write.

Closes a native Mantis panel, or a panel contributed by the same extension when the name matches its panel id or title.

js
await window.mantis.panels.close('Inspector');

Commands ​

Commands are callable operations. Extensions can register handlers and execute commands by id.

commands.registerCommand(command, handler) ​

Registers a command handler and returns a disposable.

For durable commands, register from activate(context) in the extension host:

js
exports.activate = async function activate(context) {
  const disposable = await mantis.commands.registerCommand(
    'demo.sample-panel.sayHello',
    async (name) => {
      return `hello ${name}`;
    },
  );

  context.subscriptions.push(disposable);
};

Panels can also register temporary panel-local commands:

js
const disposable = await window.mantis.commands.registerCommand(
  'demo.sample-panel.sayHello',
  async (name) => {
    return `hello ${name}`;
  },
);

// Later:
await disposable.dispose();

The command id must either:

  • be declared in contributes.commands, or
  • start with the extension id followed by .

Host-registered commands are disposed when the extension host deactivates. Panel-registered commands are disposed when the panel instance closes.

commands.execute(command, args?, mapId?) ​

Requires commands:execute.

Executes a registered extension command. If the manifest declares onCommand:<commandId>, Mantis activates the extension host before executing it.

js
const result = await window.mantis.commands.execute(
  'demo.sample-panel.sayHello',
  ['Anas'],
);

Native command execution is intentionally restricted, and the allowlist of native Mantis commands is currently empty. In practice commands.execute reaches only commands an extension registered itself; anything else throws.

Command invocation times out after 30 seconds.

Backend ​

backend.invoke(action, payload?) ​

Requires backend:invoke.

Invokes an action in the extension's Python backend.

js
const result = await window.mantis.backend.invoke('summarize', {
  pointIds: ['point-a', 'point-b'],
});

The action must be declared in backend.actions unless the extension declares an empty action list.

Events ​

events.subscribe(eventName, options, handler) ​

Subscribes to a Mantis event and resolves to a disposable. Call .dispose() on it to unsubscribe.

In panels the resolved value is additionally callable, so unsubscribe() works there. In host code it is a plain { dispose } object and calling it throws. Always use .dispose() so the same code works in both runtimes.

js
const unsubscribe = await window.mantis.events.subscribe(
  'selection.changed',
  { mapId: 'map-id' },
  (payload, meta) => {
    console.log(meta.event, payload);
  },
);

await unsubscribe.dispose();

You can omit options:

js
const unsubscribe = await window.mantis.events.subscribe(
  'maps.changed',
  (payload) => {
    console.log(payload);
  },
);

Event names ​

EventPermissionPayload
maps.changedmaps:readSame shape as maps.list().
activeMap.changedmaps:readActive view metadata or null.
selection.changedselection:readSame shape as selection.get().
bags.changedselection:readCurrent bags/global variables.

Disposable pattern ​

Any API that registers something should return a disposable:

js
const subscriptions = [];

subscriptions.push(await window.mantis.events.subscribe('maps.changed', console.log));
subscriptions.push(await window.mantis.commands.registerCommand('demo.my-ext.run', run));

async function cleanup() {
  await Promise.all(subscriptions.map((item) => item.dispose()));
}

This pattern is important because Mantis needs to clean extension resources when a panel closes, an extension host deactivates, an extension is disabled, or an extension is uninstalled.