open-wa v5 is alpha. Use v4.76.0 for mature production systems unless you are validating v5.
The Client APIAPI ExplorerLicensing

Plugin getting started

Build a small plugin that listens for a message, checks required fields, and sends a reply.

Plugin Starter Kit Wally

Plugin getting started

Use a plugin when you want reusable logic to run inside the Easy API process. A plugin can listen for events, call client methods, mount HTTP routes, add dashboard pages, or register AI tools.

What plugins can do

A plugin is an npm package (or local file) that:

  • Listens to WhatsApp events (messages, auth, lifecycle)
  • Calls WhatsApp methods (send messages, decrypt media)
  • Mounts HTTP routes at /plugins/<name>/
  • Adds pages to the dashboard sidebar
  • Registers tools for AI agents to call

Plugins run inside the Easy API process, not as separate services. They receive a security-filtered event emitter and a transport-agnostic client proxy.

Pick the right extension point

Use a plugin when...Use something else when...
Building reusable integrationsYou need a separate process (use SocketClient)
Sharing functionality across sessionsYou need direct browser control (use createClient)
Adding dashboard pagesYou only need simple event forwarding (use the webhook integration plugin)
Exposing AI tools via MCPYou need low-level runtime ownership (use embedded runtime)

Install the SDK

pnpm add @open-wa/plugin-sdk
# or
npm install @open-wa/plugin-sdk

The SDK provides:

  • createPlugin(): Type-safe plugin factory
  • defineConfig(): Zod wrapper for plugin configuration
  • z: Zod (re-exported for convenience)
  • TypeScript types for all plugin contracts

Build your first plugin

1

Create the plugin

Use the createPlugin() factory:

import { createPlugin } from '@open-wa/plugin-sdk';

function getTextMessage(message: unknown) {
  if (!message || typeof message !== 'object') return null;

  const candidate = message as { body?: unknown; from?: unknown };
  if (typeof candidate.body !== 'string' || typeof candidate.from !== 'string') {
    return null;
  }

  return { body: candidate.body, from: candidate.from };
}

export default createPlugin({
  meta: {
    name: 'greeting-bot',
    version: '1.0.0',
    description: 'Greets new contacts with a welcome message',
  },

  init: async ({ client, logger }) => ({
    'message.received': async ({ message }) => {
      const msg = getTextMessage(message);

      if (!msg) return;

      if (msg.body === 'Hi') {
        await client.sendText(msg.from, '👋 Welcome! How can I help?');
        logger.info('Sent greeting', { from: msg.from });
      }
    },
  }),
});
2

Define configuration

Use defineConfig() with Zod for typed configuration:

import { createPlugin, defineConfig, z } from '@open-wa/plugin-sdk';

function getTextMessage(message: unknown) {
  if (!message || typeof message !== 'object') return null;

  const candidate = message as { body?: unknown; from?: unknown };
  if (typeof candidate.body !== 'string' || typeof candidate.from !== 'string') {
    return null;
  }

  return { body: candidate.body, from: candidate.from };
}

const config = defineConfig(z => z.object({
  greeting: z.string().default('👋 Welcome!'),
  triggerWord: z.string().default('Hi'),
}));

export default createPlugin({
  meta: { name: 'greeting-bot' },
  configSchema: config,

  init: async ({ client, logger, config }) => ({
    'message.received': async ({ message }) => {
      const msg = getTextMessage(message);

      if (!msg) return;

      if (msg.body === config.triggerWord) {
        await client.sendText(msg.from, config.greeting);
      }
    },
  }),
});
3

Add event handlers

The init function returns a Hooks object. Available hooks include:

  • 'message.received': Incoming messages
  • 'message.sent': Outgoing messages
  • 'core.started': After the session is ready
  • 'auth.qr': When a QR code is emitted
  • 'dispose': On session shutdown

See the Hooks reference for the complete list.

4

Put the files in one project

Use a layout where the config file and plugin file live next to each other:

my-openwa-app/
  wa.config.mjs
  plugins/
    greeting-bot.mjs

Put the plugin code from Step 2 in plugins/greeting-bot.mjs. The plugin loader uses dynamic import(ref), so a local plugin should be loadable JavaScript at runtime. Do not point the runtime at ./plugins/greeting-bot.ts unless your own Node.js startup environment already installs a TypeScript loader; the current plugin loader does not compile plugin TypeScript for you.

5

Load the plugin

In wa.config.mjs, build the plugin reference from the config file location:

// wa.config.mjs
export default {
  sessionId: 'my-session',
  plugins: [
    new URL('./plugins/greeting-bot.mjs', import.meta.url).href,
  ],
  pluginConfig: {
    'greeting-bot': {
      greeting: 'Hello! Welcome to our service.',
      triggerWord: 'hello',
    },
  },
};

The plugins array accepts npm package names, scoped package names, absolute local references, and local references that Node can import. The module must export a plugin as its default export or as a named plugin export. The pluginConfig object uses the plugin name from meta.name, not the package name or file path.

6

Run from the project directory

Run the CLI from my-openwa-app/ or pass the config path explicitly:

npx @open-wa/wa-automate --config ./wa.config.mjs --port 8080

If this Easy API instance is reachable outside your machine, add --api-key "your-secure-key" so plugin-mounted routes and the main API share the same authentication boundary.

Expected local startup logs are illustrative, not captured from this docs update. A successful load should include plugin loader and registration messages similar to:

plugin_loaded { ref: 'file:///.../plugins/greeting-bot.mjs', name: 'greeting-bot', version: '1.0.0' }
plugin_registered { plugin: 'greeting-bot', version: '1.0.0' }

After the session is connected, send the trigger word from another WhatsApp account:

hello

Expected local behavior: the plugin sends Hello! Welcome to our service. back to the sender and logs the greeting action.

Expected plugin failure logs are illustrative and abbreviated. A missing file or invalid config should fail before the plugin handles messages and show a signal similar to:

plugin_load_error { ref: 'file:///.../plugins/greeting-bot.mjs', error: 'Cannot find module' }
plugin_config_invalid { plugin: 'greeting-bot', issues: [...] }

If the plugin does not load

  • Confirm the CLI loaded the intended config file. Run with --config ./wa.config.mjs when in doubt.
  • Confirm the plugin reference points to JavaScript that Node can import, such as .mjs or compiled .js.
  • Confirm the module exports default createPlugin(...) or a named plugin export.
  • Confirm meta.name is present. Missing names are skipped with a plugin_load_skip warning.
  • Confirm pluginConfig is keyed by meta.name, for example 'greeting-bot'.
  • If the greeting never appears, send the exact configured triggerWord, then check for plugin_loaded, plugin_registered, plugin_config_invalid, or plugin_load_error in the Easy API logs. After the load succeeds, move reusable behavior into a package and read Publishing a plugin.

Plugin architecture

Security-filtered event emitter

Plugins receive a filtered event emitter that:

  • Can subscribe to public events (on, once, off)
  • Cannot emit events (only the host can emit)
  • Cannot listen to internal events (launch.*, browser.*, transport.*)
  • Cannot listen to sensitive events (license.*, session data)

Transport-agnostic client proxy

Plugins get a client proxy that forwards method calls to the WhatsApp runtime. This is not direct browser access. It works through the host's transport layer (HTTP RPC / SSE).

Config validation

If you provide a configSchema, the host validates the plugin's config before calling init(). If validation fails, the plugin will not load and an error is logged.

When to use plugins

  • Building reusable integrations (CRM bridges, webhook forwarders)
  • Adding custom message processing (moderation, translation, transcription)
  • Exposing HTTP endpoints alongside the API
  • Adding dashboard pages for monitoring
  • Registering tools for AI agents
Wally the Walrus typing

Was this helpful?

Wally and his cute companion coffee mug are coding day and night to keep this up-to-date!

On this page