Skip to main content
Version: v0.7.6

Install and enable a plugin

Plugins extend a running OpenWA gateway in-process: they react to WhatsApp activity through typed lifecycle hooks and act through a narrow, permission-gated capability API — no fork, no core changes. This page shows you how to install a plugin, configure it, and enable it on a live gateway, then points you to the rest of the section.

A plugin is a self-contained folder — a manifest.json plus a compiled entry file — packaged as a .zip. It is installed disabled and runs only after an administrator explicitly enables it. Management is admin-only, over the REST API or the dashboard's Plugins page.

Prerequisites

How plugins fit together

A plugin never touches the gateway's internals. It registers handlers for hook events and calls back into the host through three capability namespaces, each gated by a permission it declared in its manifest.

The three permissions a plugin can declare:

PermissionGrantsUsed for
messages:sendctx.messages.sendText / ctx.messages.replyAuto-replies, notifications
engine:readRead-only engine queries (group info, contacts, chats, number check)Enriching events
net:fetchSSRF-guarded outbound HTTP, scoped to the manifest's net.allow host listCalling external APIs

A plugin that calls a capability it did not declare fails with a PluginCapabilityError. For the full hook list, capability surface, and lifecycle, see Plugin architecture.

Plugins run with full Node privileges

Disk-loaded plugins run in a worker_thread (V8-context isolation), but that is not an OS-level sandbox — a plugin can reach Node built-ins like fs and process. Install only plugins you trust, and review a plugin's declared permissions and sessions before enabling it.

The install flow

Installing a plugin from a .zip is a three-step sequence — install, configure, enable — because OpenWA never auto-runs freshly uploaded code.

All routes sit under the /api prefix and require an ADMIN key in the X-API-Key header. The examples below use the faq-bot plugin, which auto-replies from configurable keyword rules.

1. Install the package

Upload the .zip as multipart form field file:

curl -X POST "http://localhost:2785/api/plugins/install" \
-H "X-API-Key: YOUR_API_KEY" \
-F "file=@faq-bot.zip"

The plugin is now installed but disabled — it is not running yet:

{
"id": "faq-bot",
"name": "FAQ / Auto-Reply Bot",
"version": "0.1.4",
"type": "extension",
"status": "installed",
"builtIn": false
}
Install straight from the catalog

If your gateway is configured with the official catalog, you can skip the build-and-upload step and install by URL instead: POST /api/plugins/install-url with body { "url": "<release-zip-url>" }. The download is SSRF-guarded and restricted to an allowlisted host. See the catalog.

2. Configure it

Each plugin declares a configSchema in its manifest. Send the settings as a config object. For faq-bot, that means the reply rules and an optional fallback:

curl -X PUT "http://localhost:2785/api/plugins/faq-bot/config" \
-H "X-API-Key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"config": {
"rules": "[{\"mode\":\"contains\",\"pattern\":\"hours\",\"reply\":\"We are open 9-5, Mon-Fri.\"}]",
"fallbackReply": "Thanks! Someone will reply shortly.",
"respondInGroups": false
}
}'
{ "id": "faq-bot", "status": "installed", "config": { "rules": "[...]", "respondInGroups": false } }

Config fields marked secret: true (an API key, a service-account JSON) are masked on read and preserved on an unchanged write — so you never have to re-paste a secret to edit an unrelated field.

The host does not validate your config types

OpenWA renders configSchema into the dashboard form and redacts secret fields, but it does not enforce the schema's types on write. A well-built plugin validates its own config defensively when it starts.

3. Enable it

Enabling runs the plugin's onLoad then onEnable lifecycle and registers its hooks:

curl -X POST "http://localhost:2785/api/plugins/faq-bot/enable" \
-H "X-API-Key: YOUR_API_KEY"
{ "id": "faq-bot", "status": "enabled", "enabledAt": "2026-06-26T10:15:00.000Z" }

The plugin is now live. Send a WhatsApp message containing "hours" to a connected session and it auto-replies. Confirm the running state any time:

curl "http://localhost:2785/api/plugins/faq-bot" \
-H "X-API-Key: YOUR_API_KEY"
Enabling is always explicit

A plugin never auto-enables — not after install, and not after a gateway restart. A previously enabled plugin comes back as installed on restart and must be re-enabled. This keeps untrusted code from running silently.

Scope a plugin to specific sessions

By default a plugin that declares sessions: ["*"] acts on every session. To restrict a session-scoped plugin to a subset, set its active sessions:

curl -X PUT "http://localhost:2785/api/plugins/faq-bot/sessions" \
-H "X-API-Key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "sessions": ["sales", "support"] }'

Use ["*"] for all sessions or [] for none. The session scope a plugin may act on is fixed by its manifest — activation can narrow it but never widen it past what the manifest's sessions allows.

To give one session different settings, set a per-session config override with PUT /api/plugins/{id}/config/{sessionId} (send an empty config to clear it). The override is shallow-merged over the base config for that session's events.

Management endpoints

Every route below sits under /api and requires the ADMIN role.

Method & pathPurpose
GET /api/pluginsList installed plugins and their status
GET /api/plugins/catalogList the remote catalog, annotated with install state
GET /api/plugins/{id}Inspect one plugin (config secrets redacted)
POST /api/plugins/installInstall from an uploaded .zip (multipart field file)
POST /api/plugins/install-urlInstall by downloading a .zip from an allowlisted URL
POST /api/plugins/{id}/updateUpdate in place from a URL (keeps config + enabled state)
POST /api/plugins/{id}/enableEnable (runs onLoadonEnable)
POST /api/plugins/{id}/disableDisable and unregister its hooks
PUT /api/plugins/{id}/configUpdate base config (body { "config": { … } })
PUT /api/plugins/{id}/config/{sessionId}Set or clear a per-session config override
PUT /api/plugins/{id}/sessionsSet which sessions a scoped plugin is active for
GET /api/plugins/{id}/healthRun the plugin's healthCheck
DELETE /api/plugins/{id}Uninstall and delete files (built-ins are protected)

A plugin's status is one of installed, enabled, disabled, or error. The full request and response shapes are in the API reference.

Disable or uninstall

Disable a plugin to stop it without losing its config:

curl -X POST "http://localhost:2785/api/plugins/faq-bot/disable" \
-H "X-API-Key: YOUR_API_KEY"

Uninstall to remove it and its files entirely:

curl -X DELETE "http://localhost:2785/api/plugins/faq-bot" \
-H "X-API-Key: YOUR_API_KEY"

Troubleshooting

SymptomCauseFix
401 UnauthorizedKey missing or not ADMIN-scopedUse an ADMIN-role key in X-API-Key. See Authentication.
404 Not Found on a plugin routePlugin id not installedConfirm the id with GET /api/plugins; ids are case-sensitive.
Plugin shows installed after a restartPlugins never auto-enableRe-enable it with POST /api/plugins/{id}/enable.
Plugin is enabled but does nothingEmpty/invalid config, or wrong session scopeRe-check the config object and the plugin's active sessions.
error status after enablePlugin threw during onLoad/onEnableInspect the error field from GET /api/plugins/{id} and the gateway logs.
Capability call fails with PluginCapabilityErrorPlugin used a permission it did not declare, or an out-of-scope sessionThis is the plugin author's fix — see Building a plugin.

Next steps