Skip to content

Extension Quick Start ​

This guide builds a small extension with a background host, a panel, and a Python backend action.

Prefer the generator

If you just want a ready-to-edit project, scaffold one instead of wiring everything by hand:

bash
npm install -g yo generator-mantis
yo mantis
node pack-bundle.cjs

The generator emits a flatter tree than the one built below: extension.js at the root, panels under panel/, and a pack-bundle.cjs script that builds package.mantisx and bundle.json for you. See Yeoman generator.

1. Create the folder ​

text
hello-mantis-extension/
  mantis.extension.json
  dist/
    extension.js
    panel.js
    styles.css
  backend/
    main.py

2. Add the manifest ​

Create mantis.extension.json:

json
{
  "manifestVersion": 1,
  "id": "demo.hello-mantis",
  "name": "Hello Mantis",
  "version": "0.1.0",
  "apiVersion": "1.0.0",
  "description": "A minimal Mantis extension panel.",
  "main": "dist/extension.js",
  "activationEvents": [
    "onStartup",
    "onPanel:helloPanel",
    "onCommand:demo.hello-mantis.sayHello"
  ],
  "permissions": [
    "maps:read",
    "selection:read",
    "commands:execute",
    "backend:invoke"
  ],
  "contributes": {
    "commands": [
      {
        "id": "demo.hello-mantis.sayHello",
        "title": "Hello Mantis: Say Hello"
      }
    ],
    "panels": [
      {
        "id": "helloPanel",
        "title": "Hello Mantis",
        "entry": "dist/panel.js",
        "styles": ["dist/styles.css"]
      }
    ]
  },
  "backend": {
    "runtime": "python",
    "entry": "backend/main.py",
    "actions": ["hello"],
    "network": false
  }
}

3. Add the extension host ​

Create dist/extension.js:

js
exports.activate = async function activate(context) {
  const count = await context.workspaceState.get('activationCount', 0);
  await context.workspaceState.update('activationCount', count + 1);

  context.subscriptions.push(
    await mantis.commands.registerCommand('demo.hello-mantis.sayHello', async () => ({
      message: 'hello from the extension host',
      activationCount: count + 1,
      extensionId: context.extensionId,
    })),
  );
};

exports.deactivate = function deactivate() {};

The host runs separately from the panel. It is the right place for durable commands, background subscriptions, and extension state.

4. Add panel styles ​

Create dist/styles.css:

css
body {
  margin: 0;
  background: #020617;
  color: #e5e7eb;
  font: 13px system-ui, sans-serif;
}

.panel {
  display: grid;
  gap: 12px;
  padding: 16px;
}

button {
  width: fit-content;
  border: 1px solid #475569;
  border-radius: 8px;
  background: #1e293b;
  color: #f8fafc;
  padding: 8px 10px;
  cursor: pointer;
}

pre {
  min-height: 180px;
  overflow: auto;
  border: 1px solid #334155;
  border-radius: 10px;
  background: #020617;
  padding: 12px;
  white-space: pre-wrap;
}

5. Add the panel code ​

Create dist/panel.js:

js
(function () {
  const root = document.getElementById('root');

  root.innerHTML = [
    '<main class="panel">',
    '<h1>Hello Mantis</h1>',
    '<button id="run">Read Workspace</button>',
    '<button id="command">Run Host Command</button>',
    '<pre id="output">Ready.</pre>',
    '</main>',
  ].join('');

  const output = document.getElementById('output');

  document.getElementById('run').onclick = async () => {
    output.textContent = 'Loading...';

    try {
      const activeMap = await window.mantis.maps.getActive();
      const selection = await window.mantis.selection.get(activeMap && activeMap.mapId);
      const backend = await window.mantis.backend.invoke('hello', {
        selectedCount: Array.isArray(selection.selected) ? selection.selected.length : 0,
      });

      output.textContent = JSON.stringify({ activeMap, selection, backend }, null, 2);
    } catch (error) {
      output.textContent = error && error.message ? error.message : String(error);
    }
  };

  document.getElementById('command').onclick = async () => {
    output.textContent = 'Running host command...';

    try {
      const result = await window.mantis.commands.execute('demo.hello-mantis.sayHello');
      output.textContent = JSON.stringify(result, null, 2);
    } catch (error) {
      output.textContent = error && error.message ? error.message : String(error);
    }
  };
})();

6. Add the backend ​

Create backend/main.py:

python
def hello(payload, ctx):
    return {
        "message": "hello from Python",
        "payload": payload,
        "extension_id": ctx.get("extension_id"),
        "project_id": ctx.get("project_id"),
    }

Backend functions receive:

  • payload: the payload passed from window.mantis.backend.invoke
  • ctx: metadata including project, user, extension id, and extension version

7. Package and install ​

Zip the contents so mantis.extension.json is at the package root, or inside a single top-level folder:

text
hello-mantis-extension.zip
  mantis.extension.json
  dist/extension.js
  dist/panel.js
  dist/styles.css
  backend/main.py

In Mantis:

  1. Open a space.
  2. Open the Verticals menu.
  3. Click Import Extension.
  4. Select your .zip, .mantisx, or .json package.
  5. Review the trust dialog.
  6. Click Trust Publisher & Install.
  7. Open the contributed panel from the Extensions section.

8. Common first errors ​

If the package is rejected, check:

  • mantis.extension.json exists at the package root or inside one single top-level folder
  • every panel entry, script, and style path exists in the package
  • the manifest main path exists if declared
  • every backend entry path exists under backend/
  • permissions include the APIs you call
  • command ids are contributed or start with the extension id
  • backend actions include the action name you invoke