Multilo Marketplace Get Multilo
All extensions
Developer guide

Build a Multilo extension.

A Multilo extension runs inside the desktop app’s extension host and programs against the global multilo API — the same way the built-in grammar, style, and citation tools do.

Anatomy

An extension is two things:

  1. A manifest (manifest.json) declaring identity, what it contributes, and when it activates.
  2. A module (your package.entryPoint) exporting activate(context) and optionally deactivate().

Your first extension

/// <reference types="@multilo/extension-api" />

export function activate(context: ExtensionContext): void {
  context.subscriptions.push(
    multilo.commands.registerCommand("acme.hello", () =>
      multilo.window.showMessage("info", "Hello from my extension!"),
    ),
  );
}

export function deactivate(): void {}

Everything you register goes on context.subscriptions so it is disposed when your extension deactivates.

Contribution points

  • diagnostics — register a provider returning offset-based Diagnostic[]; the host renders them and offers one-click fixes when you set replacement.
  • commands — register handlers with multilo.commands.registerCommand; invoke them with executeCommand.
  • configuration— typed settings with defaults; users edit them in your extension’s Settings panel, and you read them with multilo.workspace.getConfiguration().get(key, fallback).
multilo.languages.registerDiagnosticProvider(
  { languages: ["markdown", "plaintext"], filePatterns: ["*.md", "*.txt"] },
  {
    provideDiagnostics(doc): Diagnostic[] {
      const out: Diagnostic[] = [];
      for (const m of doc.getText().matchAll(/\bTODO\b/g)) {
        out.push({
          kind: "todo",
          offset: m.index ?? 0,
          length: m[0].length,
          message: "unresolved TODO.",
          severity: multilo.DiagnosticSeverity.Info,
          ruleId: "acme-todo",
          suggestion: "Resolve or remove before publishing.",
        });
      }
      return out;
    },
  },
);

Activation events

Extensions are lazy by default. Declare activationEvents:

  • onStartupFinished — shortly after launch
  • onLanguage:markdown — when a matching document opens
  • onCommand:<id> — when one of your commands runs

The manifest

Validated by the JSON schema (the $schema line gives you autocomplete in most editors):

{
  "$schema": "https://www.multilo.com/schemas/extension-manifest.schema.json",
  "manifestVersion": 1,
  "extensionId": "acme.todo",
  "publisher": "acme",
  "name": "todo",
  "displayName": "TODO finder",
  "description": "Flags unresolved TODOs.",
  "version": "0.1.0",
  "runtime": "web_worker",
  "permissions": ["workspace.readText", "diagnostics.provide", "commands.register"],
  "activationEvents": ["onLanguage:markdown", "onCommand:acme.hello"],
  "engines": { "multilo": "^2.0.0" },
  "package": { "entryPoint": "dist/extension.js", "packageSizeKb": 0 },
  "capabilities": [
    { "kind": "diagnostics", "title": "TODO finder",
      "languages": ["markdown"], "filePatterns": ["*.md"], "diagnosticKinds": [] }
  ],
  "contributes": {
    "commands": [{ "command": "acme.hello", "title": "Acme: Hello" }]
  }
}

Permissions & sandbox

Extensions are sandboxed (Web Worker) and deny-by-default: the host only honors an API call if the matching permission is declared in permissions (e.g. workspace.readText, diagnostics.provide). Privileged runtimes (node, process.spawn, network.*) are gated to first-party / verified publishers.

Packaging & publishing

  1. Build your entryPoint (e.g. dist/extension.js).
  2. Zip the package; compute its SHA-256 and size; fill in package.packageUrl, packageSha256, packageSizeKb.
  3. Submit it through the Multilo marketplace for review.

The @multilo/extension-api package ships the type definitions, the manifest schema, and a Hello-World template to copy.