Starter template for creating a Bulwark Mail plugin.
npm installEdit src/index.js with your plugin code.
npm run buildThis produces dist/index.js using esbuild.
cp manifest.json dist/
cd dist
zip -r ../my-plugin.zip manifest.json index.jsUpload the ZIP via Admin → Plugins in Bulwark Mail.
- manifest.json: Update
id,name,author,description,type, andpermissions - src/index.js: Keep the slot, hooks and settings you need and remove the rest
- package.json: Update
nameto match your plugin
Draw everything with the app's UI kit. It is the app's own set of controls, so your plugin looks like the rest of Bulwark in light, dark and custom themes:
import { SidebarRow, Field, Input, DialogButtons } from '@plugin-host/ui';The host provides the kit at run time, like React, so it is not bundled.
The plugin iframe has CSS for kit components only: no Tailwind classes, no
stylesheets of your own, no hard-coded colours. Lay things out with
Stack, Row, Text and Heading.
The template shows the common pieces:
- a sidebar section (
sidebar-section): the host draws the header, titled withapi.ui.setSectionTitle, and the plugin drawsSidebarRows,SidebarEmptyand an "add" row with a plus - a dialog (
api.ui.openDialogwith theplugin-dialogslot): the body starts withFields, and the buttons go to the host's footer throughDialogButtons, Cancel first - a host-drawn command palette entry (
api.ui.setActions): buttons and menu entries on the app's surfaces are declared as data, not drawn in an iframe
Reader banners use InfoBar, settings use Section and SettingRow, full
pages use Page and PageHeader. Each component in the kit names the app
screen it copies.
test/ui.test.mjs renders the section and the dialog with a fake api
(test-utils/fake-api.mjs in the plugins repo) and a stand-in for the kit.
Run it with npx vitest run plugin-template from the repo root.
hook— Event-driven, no visible UI. Reacts to emails, calendar events, etc.ui-extension— Adds buttons, banners, or panels to existing views.sidebar-app— Adds a full panel in the sidebar area.
- React is provided by the host app — do not bundle it
- Use
api.storagefor persistent data, notlocalStoragedirectly - Every hook and slot needs its permission in
manifest.json(for exampleemail:readforonEmailOpen,ui:sidebar-widgetfor the sidebar widget), or the host skips it - Put strings users see in
localesinmanifest.jsonand read them withapi.i18n.t() - Plugin auto-disables after 3 errors in 60 seconds