Plugin Catalog
The six official plugins maintained in the openwa-plugins marketplace repository. Each entry lists what the plugin does, the permissions it declares, its configuration keys, and the minimum OpenWA version it requires.
Every plugin here is an extension that runs sandboxed in a worker thread and hooks message:received (some hook more). For the runtime model and the permission system, see Plugin Architecture. To install and configure a plugin, see Plugins Overview.
Summary
| Plugin | Does | Permissions | Status | Requires |
|---|---|---|---|---|
| after-hours | Away reply outside business hours | messages:send | stable | ≥ 0.6.2 |
| chat-flow | Stateful numbered-menu bot | messages:send | stable | ≥ 0.7.0 |
| faq-bot | Keyword/regex auto-reply | messages:send | stable | ≥ 0.6.1 |
| group-translate | In-group auto-translation | messages:send, engine:read, net:fetch | stable | ≥ 0.7.0 |
| gsheets-logger | Logs message events to a Google Sheet | (none) | stable | ≥ 0.6.1 |
| voice-transcription | Voice notes → text webhook event | net:fetch | beta | ≥ 0.7.0 |
Requires is the minOpenWAVersion. All six are documented at the versions published in the marketplace catalog; column values come from each plugin's manifest.json.
messages:send lets the plugin send and reply to messages. engine:read lets it read engine state such as group info. net:fetch lets it make outbound HTTP calls through the host's SSRF-guarded fetch, restricted to hosts in the manifest's net.allow list. A plugin that declares no permissions can only read hook events.
after-hours
Replies with a configurable away message to messages received outside business hours.
| Field | Value |
|---|---|
| Identifier | after-hours |
| Version | 0.1.2 |
| Status | stable |
| Requires OpenWA | ≥ 0.6.2 (tested 0.6.2) |
| Type | extension |
| Permissions | messages:send |
| Hooks | message:received |
| Repository | OpenWA-plugins/after-hours |
What it does. Holds a per-weekday business-hours schedule interpreted in a configurable IANA timezone. When a message arrives outside the open window for that day, it sends the configured away message as a quoted reply, throttled to at most once per chat per cooldownSec. Group chats are ignored unless respondInGroups is set. A malformed schedule or unknown timezone fails fast and shows as ERROR in the dashboard rather than misbehaving silently.
Configuration.
| Key | Required | Default | Description |
|---|---|---|---|
schedule | yes | — | JSON object mapping mon..sun to "HH:MM-HH:MM" (24-hour, open < close) or null for closed. An absent day is closed. |
timezone | no | UTC | IANA timezone the schedule is interpreted in, e.g. Asia/Jakarta. |
awayMessage | yes | — | Reply sent outside business hours. |
cooldownSec | no | 3600 | Minimum seconds between after-hours replies to the same chat. 0 replies every time. |
respondInGroups | no | false | Whether to reply in group chats. |
Example schedule:
{ "mon": "09:00-17:00", "tue": "09:00-17:00", "wed": "09:00-17:00",
"thu": "09:00-17:00", "fri": "09:00-17:00", "sat": "09:00-13:00", "sun": null }
chat-flow
An interactive, stateful auto-reply: a trigger word opens a greeting plus a numbered menu, and replies walk a configurable menu tree.
| Field | Value |
|---|---|
| Identifier | chat-flow |
| Version | 1.0.3 |
| Status | stable |
| Requires OpenWA | ≥ 0.7.0 (tested 0.7.0) |
| Type | extension |
| Permissions | messages:send |
| Hooks | message:received |
| Repository | OpenWA-plugins/chat-flow |
What it does. A trigger word (or any message, if trigger is empty) sends a greeting and a numbered menu. The user's reply selects an option; selections traverse a menu tree of arbitrary depth, where leaf nodes end the flow. State is kept per (session, chat) and expires after 15 minutes of inactivity; re-sending the trigger restarts an active flow. A reply that matches no option re-sends the current menu. The plugin is session-scoped (sessionScoped) and ships a visual flow editor (configUi) the dashboard opens in a sandboxed frame, so the tree can be designed without hand-editing JSON.
Configuration.
| Key | Required | Default | Description |
|---|---|---|---|
greeting | yes | — | The greeting plus menu sent when the flow starts. |
options | yes | — | The menu tree: an array of { key, text, options? } nodes that nest arbitrarily. |
trigger | no | "" | Word that starts the flow (case-insensitive). Empty means any message starts it. |
respondInGroups | no | false | Whether to run in group chats. |
Example options tree:
{
"trigger": "menu",
"greeting": "Hi! Reply with a number:\n1. Pricing\n2. Support",
"options": [
{ "key": "1", "text": "Plans start at Rp100.000/mo." },
{ "key": "2", "text": "Support — reply with a number:\n1. Billing\n2. Technical",
"options": [
{ "key": "1", "text": "Billing: billing@example.com" },
{ "key": "2", "text": "A ticket has been created — we'll reply shortly." }
] }
]
}
faq-bot
Auto-replies to inbound messages from configurable keyword or regex rules.
| Field | Value |
|---|---|
| Identifier | faq-bot |
| Version | 0.1.4 |
| Status | stable |
| Requires OpenWA | ≥ 0.6.1 (tested 0.6.1) |
| Type | extension |
| Permissions | messages:send |
| Hooks | message:received |
| Repository | OpenWA-plugins/faq-bot |
What it does. Matches each inbound message against an ordered list of rules. Each rule is contains, exact (both case-insensitive), or regex (compiled with the i flag). The first matching rule wins, and its reply is sent as a quoted reply. If nothing matches and fallbackReply is set, the fallback is sent, throttled per chat by fallbackCooldownSec. Group chats are ignored unless respondInGroups is set. An invalid regex rule is skipped with a warning; a structurally invalid rules value fails fast and shows as ERROR in the dashboard.
Configuration.
| Key | Required | Default | Description |
|---|---|---|---|
rules | yes | — | JSON array of { mode, pattern, reply } rules, where mode is contains, exact, or regex. |
fallbackReply | no | "" | Reply sent when no rule matches. Empty stays silent. |
fallbackCooldownSec | no | 600 | Minimum seconds between fallback replies to the same chat. 0 replies every time. |
respondInGroups | no | false | Whether to reply in group chats. |
Example rules:
[
{ "mode": "contains", "pattern": "harga", "reply": "Harga mulai Rp100.000. Ketik 'menu' untuk detail." },
{ "mode": "exact", "pattern": "menu", "reply": "1) Harga 2) Jam buka 3) Lokasi" },
{ "mode": "regex", "pattern": "^/start", "reply": "Selamat datang! Ada yang bisa kami bantu?" }
]
group-translate
Auto-translates group messages between participants' languages via a LibreTranslate backend, controlled in-chat with
/trcommands.
| Field | Value |
|---|---|
| Identifier | group-translate |
| Version | 1.0.4 |
| Status | stable |
| Requires OpenWA | ≥ 0.7.0 (tested 0.7.0) |
| Type | extension |
| Permissions | messages:send, engine:read, net:fetch |
| Hooks | message:received |
| Repository | OpenWA-plugins/group-translate |
What it does. Learns each group member's language from what they type (or pins it with /tr setlang), then posts a combined reply translating each message into the other languages present. Everything is managed in-chat with /tr commands: read-only commands are open to anyone; state-changing commands are admin-gated, resolved via ctx.engine.getGroupInfo (hence engine:read). Translation is disabled until an admin runs /tr on. Outbound translate calls go through the host's SSRF-guarded ctx.net.fetch, so the backend host must also appear in the manifest's net.allow allowlist (it ships allowing localhost:7001). A per-call timeout plus a circuit breaker back off a slow or flaky backend instead of stalling the chat.
The host denies any outbound host not in this plugin's manifest net.allow. Before packaging, set net.allow to the host:port of your libretranslateUrl (it ships allowing localhost:7001). For plugins, this per-plugin net.allow allowlist replaces the old SSRF_ALLOWED_HOSTS host-allowlist guidance from the core modules — the manifest is what opens a host. The host-level loopback guard is separate: a localhost/127.0.0.1 backend still needs SSRF_ALLOWED_HOSTS set on the host (the SSRF guard blocks loopback by default), exactly as the voice-transcription entry notes.
Configuration.
| Key | Required | Default | Description |
|---|---|---|---|
libretranslateUrl | yes | http://localhost:7001 | Base URL of your LibreTranslate instance. Its host:port must also be in the manifest net.allow. |
libretranslateApiKey | no | — | Secret API key, if your instance requires one. Redacted on read. |
timeoutMs | no | 4000 | Per-call timeout. Keep at or below the host hook budget (5000 ms). |
commandPrefix | no | /tr | The in-chat command prefix. |
minLength | no | 2 | Minimum message length to translate. |
maxLength | no | 2000 | Maximum message length to translate. |
denyReply | no | false | Reply "admins only" when a non-admin runs a restricted command. |
In-chat commands (default prefix /tr):
| Command | Who | Effect |
|---|---|---|
/tr help | anyone | Show the command list. |
/tr status | anyone | Show whether translation is on and per-participant languages. |
/tr on · /tr off | admin | Enable or disable translation in this group. |
/tr setlang <code> [@user] | admin | Pin a participant's language. |
/tr auto [@user] | admin | Resume auto-learning a participant's language. |
/tr ignore · /tr unignore [@user] | admin | Skip or resume translating a participant. |
/tr grant · /tr revoke [@user] | admin | Delegate or remove control to a non-admin participant. |
gsheets-logger
Logs every WhatsApp message event to a Google Sheet via a service account — an append-only audit trail across all sessions.
| Field | Value |
|---|---|
| Identifier | gsheets-logger |
| Version | 0.2.2 |
| Status | stable |
| Requires OpenWA | ≥ 0.6.1 (tested 0.6.1) |
| Type | extension |
| Permissions | (none) |
| Hooks | message:received, message:sent, message:failed, message:ack |
| Repository | OpenWA-plugins/gsheets-logger |
What it does. Writes one row per message event — across message:received, message:sent, message:failed, and message:ack — to a Google Sheet, using a fixed 14-column schema. It authenticates as a Google service account (JWT RS256) with no runtime dependencies. Writes are buffered and flushed in batches, with retain-on-failure (rows are kept and retried on a Sheets error), a 5000-row cap, and persistence to plugin storage so the buffer survives restarts. The plugin declares no permissions: it only reads hook events and writes to your sheet, never sending messages or reading contacts.
The 14 columns, one row per event:
timestamp | sessionId | event | direction | chatId | from | to | senderName | isGroup | type | body | messageId | ackStatus | error
Enabling the Google Sheets API on the project and sharing the spreadsheet with the service account (as Editor) are independent steps. Skipping either fails with a different 403 in the logs. See the plugin's README for the full setup walkthrough.
Configuration.
| Key | Required | Default | Description |
|---|---|---|---|
serviceAccountJson | yes | — | Full service-account key JSON. Stored as a secret. Share the sheet with this account's client_email as Editor. |
spreadsheetId | yes | — | The ID from the sheet URL, between /d/ and /edit. |
sheetTab | no | Logs | Target tab name. The tab must already exist, with a header row of your choosing — the plugin appends data rows only. |
flushIntervalSec | no | 5 | Seconds between flushes. |
flushBatchSize | no | 20 | Flush early once this many rows are buffered. |
message:ack rowsThe message:ack rows fill the messageId and ackStatus columns and require OpenWA ≥ 0.6.1; older builds never emitted the hook.
voice-transcription
Transcribes inbound voice notes to text via an OpenAI-compatible speech-to-text backend and delivers a
message.transcriptionevent to your webhook.
| Field | Value |
|---|---|
| Identifier | voice-transcription |
| Version | 1.0.0 |
| Status | beta |
| Requires OpenWA | ≥ 0.7.0 (tested 0.7.3) |
| Type | extension |
| Permissions | net:fetch |
| Hooks | message:received |
| Repository | OpenWA-plugins/voice-transcription |
What it does. On each inbound voice note, runs speech-to-text against any OpenAI-compatible /v1/audio/transcriptions endpoint (self-hosted Speaches/faster-whisper, or hosted Groq/OpenAI) and POSTs a message.transcription event to your delivery webhook — so a bot or AI can read and reply to audio. Transcription runs off the message-delivery path as an un-awaited task: it never touches the message.received payload, never blocks delivery, and is not bound by the 5-second hook budget. It delivers completed (with the transcript), failed, or skipped status, so a consumer always knows a voice note arrived even when it can't be read. The plugin is in beta and disabled until enabled.
transcription.text is attacker-controlled speech; the event marks it untrusted: true. A downstream LLM responder must place it in a user role, never a system or trusted context — a caller can speak injection instructions a typist never would.
Delivered event (POSTed to deliveryWebhookUrl):
{
"event": "message.transcription",
"sessionId": "…",
"messageId": "<waMessageId>",
"chatId": "…@s.whatsapp.net",
"status": "completed",
"source": "speech-to-text",
"untrusted": true,
"transcription": { "text": "…", "language": "es", "provider": "faster-whisper", "model": "small" }
}
Correlate it to the original voice note by messageId. It arrives shortly after message.received, out of order — do not assume ordering. When deliverySecret is set, the body is HMAC-SHA256 signed in X-OpenWA-Signature: sha256=<hex>, the same scheme as core webhooks.
Configuration.
| Key | Required | Default | Description |
|---|---|---|---|
sttBaseUrl | yes | — | OpenAI-compatible STT base URL (/v1/audio/transcriptions is appended). Host must be in net.allow; a localhost target also needs SSRF_ALLOWED_HOSTS on the host. |
sttApiKey | no | — | Bearer key for a hosted backend (Groq/OpenAI). Blank for a local Speaches instance. Stored redacted. |
model | no | small | Whisper model name, e.g. small, base, whisper-large-v3-turbo. |
language | no | (auto) | Optional BCP-47 hint. Blank auto-detects. |
provider | no | faster-whisper | Informational label recorded in the delivered event. |
timeoutMs | no | 20000 | Per-request STT timeout. Min 1000, max 30000. |
enabledMessageTypes | no | ["voice"] | Message types to transcribe. Add audio to also transcribe non-PTT audio (more cost). |
maxSizeBytes | no | 16777216 | Skip audio larger than this. |
maxPerHour | no | 60 | Best-effort per-session hourly transcription cap. |
deliveryWebhookUrl | cond. | — | Endpoint receiving the event. Host must be in net.allow. Optional if you only use chatDelivery. |
deliverySecret | no | — | HMAC-SHA256 signs the body in X-OpenWA-Signature. Stored redacted. |
deliveryTimeoutMs | no | 5000 | Delivery POST timeout. Min 1000, max 30000. |
chatDelivery | no | off | Also post the transcript into WhatsApp: off (webhook only), self (a note to your own number), or reply (quote-reply to the sender). |
This plugin ships net.allow for localhost, 127.0.0.1, api.groq.com:443, and api.openai.com:443. For any other STT or delivery host, add its host:port to net.allow and re-package. For a deeper how-to, see Voice Transcription.