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:
npm install -g yo generator-mantis
yo mantis
node pack-bundle.cjsThe 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
hello-mantis-extension/
mantis.extension.json
dist/
extension.js
panel.js
styles.css
backend/
main.py2. Add the manifest
Create mantis.extension.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:
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:
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:
(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:
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 fromwindow.mantis.backend.invokectx: 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:
hello-mantis-extension.zip
mantis.extension.json
dist/extension.js
dist/panel.js
dist/styles.css
backend/main.pyIn Mantis:
- Open a space.
- Open the Verticals menu.
- Click Import Extension.
- Select your
.zip,.mantisx, or.jsonpackage. - Review the trust dialog.
- Click Trust Publisher & Install.
- Open the contributed panel from the Extensions section.
8. Common first errors
If the package is rejected, check:
mantis.extension.jsonexists at the package root or inside one single top-level folder- every panel
entry,script, andstylepath exists in the package - the manifest
mainpath exists if declared - every backend
entrypath exists underbackend/ - permissions include the APIs you call
- command ids are contributed or start with the extension id
- backend actions include the action name you invoke