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.
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.
| Member | Panel (window.mantis) | Host (mantis) |
|---|---|---|
extension.getManifest | Yes | Yes |
maps.list, maps.getActive, maps.getPoints | Yes | Yes |
maps.open | Yes | No |
clusters.list | Yes | Yes |
selection.get | Yes | Yes |
selection.set | Yes, but not installable (see below) | No |
bags.list, bags.create | Yes | Yes |
panels.open, panels.close | Yes | Yes |
commands.execute, commands.registerCommand | Yes | Yes |
backend.invoke | Yes | Yes |
events.subscribe | Yes | Yes |
workspaceState, globalState at the top level | Yes | No, use context.workspaceState / context.globalState |
rpc | Yes | Yes |
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
window.mantis.rpc(method, payload?)Low-level escape hatch used by the SDK helpers.
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().
exports.activate = async function activate(context) {
context.subscriptions.push(
await mantis.commands.registerCommand('demo.my-ext.run', run),
);
};The context includes:
| Field | Description |
|---|---|
extensionId | Manifest extension id. |
extensionUri | Virtual URI for this installed extension. |
manifest | Normalized manifest. |
apiVersion | Requested SDK version. |
subscriptions | Disposables cleaned up on host shutdown. |
workspaceState | State scoped to the current space and extension. |
globalState | State scoped to the extension across all spaces. |
State API:
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.
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).
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.
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.
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.
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.
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.
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:
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.
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.
const bags = await window.mantis.bags.list();bags.create(name, pointIds, mapId?)
Requires bags:write.
Creates a bag from point ids.
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.
await window.mantis.panels.open('Inspector');// 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.
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:
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:
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.
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.
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.
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:
const unsubscribe = await window.mantis.events.subscribe(
'maps.changed',
(payload) => {
console.log(payload);
},
);Event names
| Event | Permission | Payload |
|---|---|---|
maps.changed | maps:read | Same shape as maps.list(). |
activeMap.changed | maps:read | Active view metadata or null. |
selection.changed | selection:read | Same shape as selection.get(). |
bags.changed | selection:read | Current bags/global variables. |
Disposable pattern
Any API that registers something should return a disposable:
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.