---
url: https://docs.ogmabox.com/nl/plugins/README.md
description: >-
  Bouw, verpak, installeer, activeer en distribueer Ogma-plugins met
  backendlogica, frontendpanelen, opdrachten, machtigingen en
  marktplaatsmetadata.
---

# Ogma-pluginsysteem {#ogma-plugin-system}

Ogma-plugins breiden de tool uit met eigen backendlogica, panelen in de frontendinterface en workflowstappen. Plugins worden lokaal geïnstalleerd vanuit een map op schijf, per project ingeschakeld en uitgevoerd in een afgeschermde omgeving.

Dit document is het primaire naslagwerk voor pluginauteurs.

Gebruik [Snel aan de slag met plugins](/nl/plugins/quickstart) als je zo snel mogelijk wilt beginnen.

***

## Snel aan de slag {#quick-start}

### Minimale backendplugin {#minimal-backend-plugin}

```
my-plugin/
  manifest.json
  backend/script.js
```

`manifest.json`:

```json
{
  "id": "my-plugin",
  "name": "My Plugin",
  "version": "1.0.0",
  "plugins": [
    {
      "kind": "backend",
      "id": "my-plugin-backend",
      "entrypoint": "backend/script.js"
    }
  ]
}
```

`backend/script.js` (ES2020; imports zijn mogelijk wanneer je een bundler gebruikt):

```js
async function init(sdk) {
  sdk.console.log("my-plugin started");

  sdk.events.onInterceptResponse(function(req, res) {
    if (res.getCode() === 403) {
      sdk.console.warn("403 on " + req.getUrl());
    }
  });
}
```

Dit volstaat voor een werkende plugin met alleen een backend. Houd de code in één bestand, `backend/script.js`, en laat `manifest.json` er rechtstreeks naar verwijzen.

### Buildproces in één minuut (TypeScript-broncode) {#one-minute-build-flow-typescript-source}

Gebruik deze structuur als je in TypeScript schrijft:

```text
my-plugin/
  manifest.json
  backend/
    src/index.ts
```

Bouwen:

```bash
pnpm add -D @ogmabox/ogma-sdk esbuild typescript
pnpm exec esbuild backend/src/index.ts --bundle --format=iife --platform=neutral --external:@ogma/sdk --external:@ogmabox/ogma-sdk --outfile=backend/script.js
```

Installeren: **Plug-ins > Installeren**, selecteer de map `my-plugin/`. Schakel de plugin vervolgens in.

***

## Manifestnaslagwerk {#manifest-reference}

`manifest.json` staat in de hoofdmap van het pakket. Alle velden zijn hoofdlettergevoelig.

### Velden op het hoogste niveau {#top-level-fields}

| Veld | Verplicht | Type | Opmerkingen |
|-------|----------|------|-------|
| `id` | ja | string | Alleen kleine letters, cijfers en koppeltekens. Maximaal 64 tekens. Uniek onder de geïnstalleerde plugins. |
| `version` | ja | string | Semver: `MAJOR.MINOR.PATCH` |
| `name` | nee | string | Weergavenaam in de interface. Standaard `id`. |
| `description` | nee | string | Samenvatting van één regel. |
| `author` | nee | object | `{ "name": "...", "email": "...", "url": "..." }` |
| `homepage` | nee | string | URL van de broncoderepository of documentatie. |
| `plugins` | ja | array | Eén of meer vermeldingen van plugincomponenten (zie hieronder). |
| `permissions` | nee | array | Lijst met namen van vereiste machtigingen (zie [Machtigingen](#permissions)). |

### Vermelding van een plugincomponent {#plugin-component-entry}

Elk object in de array `plugins` beschrijft één component.

**Backendcomponent:**

```json
{
  "kind": "backend",
  "id": "my-plugin-backend",
  "entrypoint": "backend/script.js",
  "runtime": "javascript",
  "assets": "backend/assets"
}
```

**Frontendcomponent:**

```json
{
  "kind": "frontend",
  "id": "my-plugin-frontend",
  "entrypoint": "frontend/script.js",
  "style": "frontend/style.css",
  "assets": "frontend/assets",
  "backend": { "id": "my-plugin-backend" }
}
```

| Veld | Verplicht | Opmerkingen |
|-------|----------|-------|
| `kind` | ja | `"backend"` of `"frontend"` |
| `id` | ja | Uniek binnen het manifest. Kleine letters, koppeltekens. |
| `entrypoint` | ja | Relatief pad naar het JS-toegangspuntbestand. |
| `style` | nee | CSS-bestand dat in het plugin-iframe wordt geladen. |
| `assets` | nee | Map met statische assets die worden aangeboden onder `/plugins/{id}/assets/`. |
| `backend.id` | nee | Koppelt een frontendcomponent aan zijn backendcomponent voor `sdk.backend.*`-RPC. |
| `runtime` | nee (alleen backend) | `"javascript"` (standaardwaarde en de enige ondersteunde waarde). |

***

## Backendplugin-API (sdk) {#backend-plugin-api-sdk}

Het backendobject `sdk` wordt doorgegeven aan je functie `init(sdk)`. Alle methoden zijn synchroon, tenzij ze als `async` zijn gemarkeerd.

### `sdk.console` {#sdk-console}

```js
sdk.console.log("message")
sdk.console.warn("message")
sdk.console.error("message")
```

Schrijft naar de logbuffer van de plugin (zichtbaar op het tabblad Logboeken). Er worden maximaal 500 items bewaard. Elk bericht wordt afgekapt op 1 KB.

### `sdk.meta` {#sdk-meta}

```js
sdk.meta.id()          // > string: plugin id (e.g. "my-plugin")
sdk.meta.packageId()   // > string: same as id
sdk.meta.version()     // > string: semver (e.g. "1.0.0")
sdk.meta.path()        // > string: writable data directory for this plugin
```

`sdk.meta.path()` verwijst naar de beschrijfbare privégegevensmap van de plugin, bijvoorbeeld `~/.local/share/ogma/plugins/my-plugin/data`. De map wordt automatisch aangemaakt en kan worden gebruikt om plugingegevens tussen herstarts te bewaren.

`sdk.storage`, `sdk.path` en `sdk.fs` zijn ook beschikbaar als hulpfuncties voor toestand en bestanden.

### `sdk.storage` {#sdk-storage}

```js
sdk.storage.get("key")      // > string | null
sdk.storage.set("key", "value")
sdk.storage.delete("key")
sdk.storage.clear()
sdk.storage.keys()          // > string[]
```

`sdk.storage` slaat gegevens per plugin-ID op en bewaart ze wanneer de plugin opnieuw wordt gestart.

### `sdk.fs` {#sdk-fs}

```js
sdk.fs.readFile("relative/file.txt")      // > string
sdk.fs.writeFile("relative/file.txt", "text")
sdk.fs.appendFile("relative/file.txt", "more")
sdk.fs.exists("relative/file.txt")        // > boolean
sdk.fs.existsSync("relative/file.txt")    // > boolean
sdk.fs.list("relative/dir")              // > string[]
sdk.fs.mkdir("relative/dir")
```

`sdk.fs` is beperkt tot bestanden onder `sdk.meta.path()`.

`read` en `write` blijven compatibiliteitsaliassen voor `readFile` en `writeFile`. Gebruik `exists` of `existsSync` voordat je een bestand aanmaakt dat je niet wilt overschrijven. Deze API's werken synchroon in de pluginruntime; ze vormen niet de volledige Node.js-module `fs`. Bestandstoegang voor plugins vereist de machtiging `plugin_storage` en blijft binnen de privégegevensmap van de plugin. Workflow-JavaScript heeft een andere bestandssysteemcontext; zie [Bestandstoegang voor workflows](../app/workflows.md#javascript-and-files).

### `sdk.path` {#sdk-path}

```js
sdk.path.join("a", "b", "c")
sdk.path.basename("/tmp/file.txt")
sdk.path.dirname("/tmp/file.txt")
sdk.path.extname("file.txt")
sdk.path.resolve("/a", "b")
sdk.path.isAbsolute("/tmp/file.txt")
sdk.path.sep
```

### `sdk.events` {#sdk-events}

Registreer callbacks voor Ogma-gebeurtenissen. Alle callbacks worden synchroon aangeroepen binnen de QuickJS-sandbox.

```js
sdk.events.onInterceptRequest(function(req) {
  // req: RequestSpecRaw
  // Return a modified RequestSpecRaw to mutate the request.
  // Return null/undefined to pass through unchanged.
});

sdk.events.onInterceptResponse(function(req, res) {
  // req: Request (read-only), res: Response (read-only)
  // Return value is ignored.
});

sdk.events.onProjectChange(function() {
  // no callback args
});

sdk.events.onFindingCreated(function(finding) {
  // finding: { id, title, reporter }
});
```

### `sdk.requests` {#sdk-requests}

```js
// Get a single HTTP entry by id
var entry = sdk.requests.get("entry-id");
// entry: { id, method, host, path, query, tls, ... } or null

// Search HTTP history
var results = sdk.requests.search({ limit: 20, offset: 0 });
// results: { entries: [...], total: N }

// Send an HTTP request (requires send_requests permission)
var response = await sdk.requests.send(spec);
// spec: RequestSpecRaw (see below)
// response: Response
```

`sdk.requests.send` vereist dat de machtiging `send_requests` in het manifest is gedeclareerd en door de gebruiker is verleend. Zie [Machtigingen](#permissions).

### `sdk.findings` {#sdk-findings}

```js
// Create a finding (requires write_findings permission)
sdk.findings.create({
  title: "SSRF via redirect",
  reporter: "my-plugin",
  dedupeKey: "ssrf-" + request.getId(),
  request: { id: request.getId() }
});

// Check if a finding already exists (dedup check)
var exists = sdk.findings.exists({ dedupeKey: "ssrf-abc" });

// List findings
var page = sdk.findings.list({ limit: 20, offset: 0 });

// Get a single finding
var finding = sdk.findings.get("finding-id");
```

Frequentielimieten voor `sdk.findings.create`: 10 per minuut, 500 per pluginsessie, 3 per gebeurteniscallback.

### `sdk.api` {#sdk-api}

Registreer backend-RPC-functies die de frontend via `sdk.backend.*` kan aanroepen:

```js
// In backend init:
sdk.api.register("getScans", function(scanId) {
  return { scans: [] };
});

// Emit an event to connected frontends:
sdk.api.send("scan:complete", { scanId: 1, status: "ok" });
```

De handler ontvangt de argumenten die vanuit de frontend worden doorgegeven (er wordt geen extra `sdk`-argument geïnjecteerd). Retourwaarden worden als JSON geserialiseerd en teruggestuurd naar de aanroeper.

De frontend roept deze aan via `sdk.backend.getScans(scanId)` - zie [Frontendplugin-API](#frontend-plugin-api-sdk).

`sdk.api.send` plaatst gebeurtenissen in een wachtrij per plugin (maximaal 200 items). Frontends pollen deze wachtrij via `sdk.backend.onEvent`.

### `sdk.replay`, `sdk.projects`, `sdk.scope`, `sdk.workflows`, `sdk.matchReplace` {#sdk-replay-sdk-projects-sdk-scope-sdk-workflows-sdk-matchreplace}

Deze naamruimten bieden alleen leesquery's. Zie het [naslagwerk voor de backend-SDK](./backend-sdk.md) voor de volledige methodesignaturen.

### Verzoekklassen {#request-classes}

**`RequestSpecRaw`** - vertegenwoordigt een onderschept verzoek. Je ontvangt dit in `onInterceptRequest`.

```js
spec.getMethod()          // > string
spec.setMethod("POST")
spec.getHost()            // > string
spec.setHost("example.com")
spec.getPort()            // > number
spec.getPath()            // > string
spec.setPath("/new/path")
spec.getQuery()           // > string
spec.getTls()             // > boolean
spec.getHeaders()         // > Record<string, string[]>
spec.setHeader("X-Foo", "bar")
spec.getBody()            // > Body | null
spec.setBody("new body")
spec.getRaw()             // > Uint8Array (raw bytes) or []
spec.setRaw(bytes)        // set raw bytes

// Create a new spec from a URL string:
var spec = new RequestSpecRaw("https://example.com/path?q=1");
```

**`Request`** - een vastgelegd verzoek dat alleen kan worden gelezen (uit `sdk.requests.get`).

```js
req.getId()
req.getMethod()
req.getHost()
req.getPort()
req.getTls()
req.getPath()
req.getQuery()
req.getUrl()           // > full URL string
req.getHeaders()       // > Record<string, string>
req.getHeader("name")
req.getBody()          // > Body | null
req.getCreatedAt()     // > Date
req.toSpec()           // > RequestSpec (mutable copy)
```

**`Response`** - een vastgelegde respons die alleen kan worden gelezen.

```js
res.getCode()            // > number (HTTP status)
res.getHeaders()         // > Record<string, string>
res.getHeader("name")
res.getBody()            // > Body | null
res.getRoundtripTime()   // > number (ms)
res.getCreatedAt()       // > Date
```

**`Body`**:

```js
body.toText()    // > string
body.toJson()    // > parsed object or null
body.toRaw()     // > Uint8Array
body.length      // > number (original size, may differ from toText() if truncated)
```

***

## Frontendplugin-API (sdk) {#frontend-plugin-api-sdk}

De code van frontendplugins wordt uitgevoerd in een afgeschermd iframe dat wordt geladen vanuit `/plugins/{id}/ui`. Het iframe gebruikt `postMessage` om met de Ogma-host te communiceren, die aanroepen doorgeeft aan de backend.

De SDK is beschikbaar via `window.ogmaSDK`. Roep `ogmaSDK.ready(cb)` aan om de actieve SDK te ontvangen zodra de hostbridge tot stand is gebracht:

```js
ogmaSDK.ready(function(sdk) {
  // sdk is the live SDK - safe to call any method here
  sdk.log.info("frontend ready");
});
```

Alle SDK-methoden retourneren Promises.

### `sdk.log` {#sdk-log}

```js
sdk.log.info("message")
sdk.log.warn("message")
sdk.log.error("message")
```

### `sdk.meta` {#sdk-meta-1}

```js
var meta = await sdk.meta.get();
// { pluginId, packageId, name, version, ogmaVersion }
```

### `sdk.requests` {#sdk-requests-1}

```js
var entry = await sdk.requests.get({ id: "entry-id" });
var result = await sdk.requests.search({ limit: 20, offset: 0, query: "host:example.com" });
```

Vereist de machtiging `read_http_history` (automatisch verleend; geen goedkeuring van de gebruiker nodig).

### `sdk.findings` {#sdk-findings-1}

```js
var page = await sdk.findings.list({ limit: 20, offset: 0 });
```

Vereist de machtiging `read_findings` (automatisch verleend).

### `sdk.scope` {#sdk-scope}

```js
var scope = await sdk.scope.getActive();
```

### `sdk.projects` {#sdk-projects}

```js
var project = await sdk.projects.getCurrent();
```

### `sdk.backend` - backend-RPC {#sdk-backend-backend-rpc}

Roep functies aan die op de backend zijn geregistreerd met `sdk.api.register`:

```js
// Call a named backend function
var result = await sdk.backend.call("getScans", [scanId]);

// Poll for backend-emitted events (sdk.api.send on the backend side)
var { events, next_since } = await sdk.backend.poll(since);
// events: [{ event: "scan:complete", args: [...] }]

// Register an event listener (uses polling internally)
sdk.backend.onEvent("scan:complete", function(data) {
  console.log("scan done", data);
});
```

`sdk.backend.onEvent` gebruikt intern een pollinglus met een interval van 2 seconden. Stop met luisteren door de geretourneerde afmeldfunctie aan te roepen:

```js
var unsub = sdk.backend.onEvent("scan:complete", handler);
// later:
unsub();
```

### `sdk.navigation` {#sdk-navigation}

```js
await sdk.navigation.addPage("/my-plugin", { title: "My Plugin" });
```

Registreert een navigatiepagina. Wordt momenteel door de host bevestigd. Volledige routerintegratie is in ontwikkeling.

### `sdk.sidebar` {#sdk-sidebar}

```js
await sdk.sidebar.registerItem("My Plugin", "/my-plugin", { icon: "puzzle" });
```

Registreert een zijbalkitem. Momenteel lokaal binnen het interfacepaneel van de plugin - de aansluiting op de globale zijbalk is in ontwikkeling.

### `sdk.commands` {#sdk-commands}

```js
await sdk.commands.register("my-plugin:scan", {
  name: "Scan with My Plugin",
  handler: function(context) { /* ... */ }
});
```

Wordt door de host bevestigd. Integratie met het opdrachtenpalet is in ontwikkeling.

### `sdk.menu` {#sdk-menu}

```js
await sdk.menu.registerItem({
  type: "Request",
  commandId: "my-plugin:scan",
  leadingIcon: "shield"
});
```

Wordt door de host bevestigd. Het invoegen van items in het contextmenu is in ontwikkeling.

### `sdk.window` {#sdk-window}

```js
sdk.window.showToast("Scan complete", { variant: "success", duration: 3000 });
```

Toont een toastmelding in het pluginpaneel. Varianten: `info`, `success`, `warning`, `error`.

### `sdk.ui` {#sdk-ui}

```js
sdk.ui.resize(600);                              // request iframe height change
sdk.ui.sidebar.registerItem("name", "/path");    // alias for sdk.sidebar.registerItem
```

***

## Machtigingen {#permissions}

Declareer machtigingen in `manifest.json`:

```json
{
  "permissions": ["send_requests", "write_findings"]
}
```

### Automatisch verleende machtigingen (geen goedkeuring van de gebruiker nodig) {#auto-granted-permissions-no-user-approval-needed}

Deze worden altijd aan elke geïnstalleerde plugin verleend:

| Machtiging | Wat deze toestaat |
|------------|----------------|
| `read_http_history` | `sdk.requests.get`, `sdk.requests.search` |
| `read_findings` | `sdk.findings.get`, `sdk.findings.list` |
| `read_scope` | `sdk.scope.getActive` |
| `read_projects` | `sdk.projects.getCurrent`, `sdk.projects.list` |
| `plugin_storage` | `sdk.storage`, `sdk.fs`, `sdk.path` |

### Beschermde machtigingen (vereisen goedkeuring van de gebruiker) {#protected-permissions-require-user-approval}

Deze moeten in het manifest worden gedeclareerd en expliciet door de gebruiker worden verleend via het tabblad Rechten:

| Machtiging | Wat deze toestaat |
|------------|----------------|
| `send_requests` | `sdk.requests.send` - uitgaande HTTP-verzoeken versturen |
| `write_findings` | `sdk.findings.create`, `sdk.findings.update` |

De gebruiker wordt om toestemming gevraagd bij het inschakelen van een plugin die beschermde machtigingen declareert. Machtigingen kunnen ook op elk moment worden verleend of ingetrokken via het tabblad Rechten.

***

## Structuur van een pluginpakket {#plugin-package-structure}

Een pluginpakket kan vanuit een lokale map worden geïnstalleerd en, bij gebruik via de browser, ook vanuit `.zip`-exports.

```
my-plugin/
  manifest.json           - verplicht
  backend/
    script.js             - gebundelde backend-JS (ES2020)
  frontend/
    script.js             - gebundelde frontend-JS
    style.css             - optionele CSS
    assets/               - statische assets (afbeeldingen, lettertypen, enz.)
```

### Vereisten voor het backendscript {#backend-script-requirements}

* Moet één zelfstandig JS-bestand zijn.
* `require()` en dynamische `import()` worden niet ondersteund.
* Moet een functie `init(sdk)` exporteren (of deze globaal definiëren).
* ES2020-subset die QuickJS ondersteunt: `async/await`, `Promise`, `Map`, `Set`, `Symbol`, `Proxy`, `Date`, `RegExp`, `JSON`. Geen `fetch`, geen `Buffer`.
* Ondersteunde statische imports worden door pluginvoorverwerking opgelost: `@ogma/sdk`, `crypto`, `fs`, `path`.
* Maximale bestandsgrootte: 256 KB.

### Vereisten voor het frontendscript {#frontend-script-requirements}

* Wordt uitgevoerd in een afgeschermd iframe. `connect-src: 'self'` is toegestaan, zodat de plugin POST-verzoeken kan versturen naar `/plugins/{id}/api/*` en `/plugins/{id}/events/poll` kan pollen.
* CSP: `default-src 'none'; script-src 'nonce-...'; style-src 'self'; img-src data: blob: 'self'; connect-src 'self'`.
* Geen `allow-same-origin` in de iframe-sandbox - de plugin heeft geen toegang tot de bovenliggende DOM of cookies van Ogma.
* Gebruik `ogmaSDK.ready(cb)` om toegang te krijgen tot de SDK; roep geen SDK-methoden aan voordat de callback wordt uitgevoerd.

***

## Een plugin bouwen voor Ogma {#building-a-plugin-for-ogma}

Omdat de backend één gebundeld JS-bestand moet zijn, moet je je TypeScript-/ES-modulebroncode bundelen voordat je de plugin installeert.

Aanbevolen toolchain:

```bash
# Install dependencies
pnpm install

# Bundle backend (outputs a single CJS/IIFE file):
esbuild packages/backend/src/index.ts \
  --bundle \
  --platform=neutral \
  --format=iife \
  --global-name=_plugin \
  --outfile=dist/backend/script.js \
  --external:@ogma/sdk --external:@ogmabox/ogma-sdk

# Bundle frontend:
vite build packages/frontend --outDir ../../dist/frontend
```

Als je de Caido-ontwikkeltoolchain (`@caido-community/dev`) gebruikt, voer dan `caido-dev build` uit en kopieer de uitvoer vervolgens naar een Ogma-compatibele structuur met `manifest.json` in de hoofdmap.

***

## Een plugin installeren {#installing-a-plugin}

1. Open **Plug-ins** in de linkerzijbalk.
2. Klik op **Installeren** (bovenaan het tabblad Geïnstalleerd).
3. Klik in de desktopapp op **Bladeren** om de native mapkiezer te openen. Typ in de browser het volledige pad naar de pluginmap op de server.
4. Klik op **Valideren** om het manifest en de bestandsinventaris te controleren.
5. Klik op **Installeren** als de validatie slaagt.
6. Selecteer de plugin in de lijst en klik op **Inschakelen**.
7. Als de plugin beschermde machtigingen declareert, bekijk en verleen deze dan op het tabblad **Rechten** voordat je de plugin inschakelt.

***

## Problemen oplossen {#troubleshooting}

**Plugininitialisatie mislukt zonder melding:** Controleer het tabblad **Logboeken**. De meest voorkomende oorzaken:

* `sdk.meta.path()` is aangeroepen, maar de gegevensmap kon niet worden aangemaakt.
* Een niet-afgevangen uitzondering in `init()`.
* Een ontbrekende of verkeerd gespelde `sdk.*`-aanroep.

**De frontend blijft leeg:** Controleer de browserconsole op CSP-overtredingen. Zorg dat je frontendscript `ogmaSDK.ready(cb)` aanroept voordat het een SDK-methode gebruikt.

**`sdk.requests.send` geeft de fout `Permission denied`:** De machtiging `send_requests` moet in het manifest zijn gedeclareerd EN door de gebruiker zijn verleend op het tabblad Rechten.

**Functies van `sdk.api.register` zijn niet aanroepbaar vanuit de frontend:** De backend moet zijn ingeschakeld (alleen geïnstalleerd zijn is niet voldoende). De functienaam moet exact overeenkomen (hoofdlettergevoelig) met wat de frontend doorgeeft aan `sdk.backend.call`.

**Compatibiliteitswaarschuwingen worden als fouten getoond:** Deze blokkeren de werking niet, maar wijzen op ontbrekende onderdelen in de API-interface. Zie de SDK-koppelingstabellen hierboven voor de API-dekking.
