Plugin Development Guide
This guide covers setting up a development environment, understanding the plugin manifest, structuring your codebase, and implementing the plugin interface.
Prerequisites and Environment Setupβ
Plugin development requires Node.js version 18 or later, the Yarn package manager, and knowledge of TypeScript and the Electron application model.
mkdir my-plugin && cd my-plugin
npm init -y
npm install --save-dev typescript webpack webpack-cli ts-loader \
copy-webpack-plugin terser-webpack-plugin @types/node electron
The Plugin Manifestβ
Every plugin must include a manifest.json at its root:
{
"name": "My Plugin",
"version": "1.0.0",
"author": "Author Name",
"category": "Productivity",
"description": "A concise description of plugin functionality",
"main": "index.bundle.js"
}
Required fields: name, version, main.
Optional fields: description, renderer, author, category, logo.
Repository Structureβ
Minimal Plugin Structureβ
my-plugin/
βββ manifest.json # Plugin metadata (required)
βββ package.json # NPM package configuration
βββ index.ts # Main process entry point (required)
βββ webpack.config.js # Build pipeline configuration
βββ tsconfig.json # TypeScript compiler configuration
βββ README.md # Developer documentation
βββ ui/ # Renderer UI assets (optional)
β βββ index.html
βββ build/ # Compiled output directory (generated)
βββ index.bundle.js
βββ manifest.json
βββ package.json
Advanced Plugin Structure (with Window Management)β
my-plugin/
βββ manifest.json
βββ package.json
βββ index.ts # Main plugin class
βββ window.ts # Window lifecycle management
βββ config.ts # Configuration persistence
βββ preload.ts # Renderer process IPC bridge
βββ webpack.config.js
βββ tsconfig.json
βββ ui/
β βββ index.html
βββ assets/
β βββ icon.png
βββ build/
Selecting a Templateβ
| Template | Bundle Size | Best Suited For |
|---|---|---|
plugin-template-html (HTML/CSS/JS) | ~60 KB | Simple settings pages, minimal interactivity |
plugin-template-react (React 18+) | ~200 KB | Component-oriented UIs, reactive state management |
plugin-template-angular (Angular 19+) | ~350 KB | Complex multi-view interfaces, enterprise-grade maintainability |
Begin with the HTML template for prototyping, graduating to React or Angular as complexity warrants.
The Main Plugin Classβ
All plugins implement the IPlugin interface:
import { MenuItemConstructorOptions } from "electron";
class MyPlugin {
public initialize(): void {
console.log("Plugin initializing");
}
public async activate(): Promise<void> {
console.log("Plugin activating");
}
public deactivate(): void {
console.log("Plugin deactivating");
}
public dispose(): void {
console.log("Plugin disposing");
}
public get menu(): MenuItemConstructorOptions {
return {
label: "My Plugin",
submenu: [
{
label: "Open",
accelerator: "CmdOrCtrl+Shift+M",
click: async () => {
/* ... */
},
},
],
};
}
}
export default new MyPlugin();
The module must export a singleton instance as its default export.
Window Managementβ
Plugins with graphical interfaces must manage their Electron BrowserWindow through a dedicated window class. Key patterns:
- Create windows with
show: falseto prevent premature display - Call
show()explicitly withinactivate() - Remove all IPC handlers and close windows in
dispose()
Configuration Managementβ
Persistent configuration is managed through electron-store:
import Store from "electron-store";
export interface MyPluginSettings {
enabled: boolean;
refreshInterval: number;
}
export class MyPluginConfig {
private store: Store<MyPluginSettings>;
private readonly defaults: MyPluginSettings = {
enabled: true,
refreshInterval: 30,
};
constructor() {
this.store = new Store<MyPluginSettings>({
name: "my-plugin-settings",
defaults: this.defaults,
});
}
public getSettings(): MyPluginSettings {
return this.store.store;
}
public updateSettings(settings: Partial<MyPluginSettings>): void {
const errors = this.validate(settings);
if (errors.length > 0) {
throw new Error(`Invalid settings: ${errors.join("; ")}`);
}
for (const [key, value] of Object.entries(settings)) {
this.store.set(key as keyof MyPluginSettings, value);
}
}
private validate(settings: Partial<MyPluginSettings>): string[] {
const errors: string[] = [];
if (
settings.refreshInterval !== undefined &&
settings.refreshInterval < 1
) {
errors.push("Refresh interval must be at least 1 second");
}
return errors;
}
}
The Preload Scriptβ
The preload script constitutes the security boundary between main and renderer processes:
import { contextBridge, ipcRenderer } from "electron";
interface MyPluginAPI {
performAction: (data: unknown) => Promise<unknown>;
closeWindow: () => void;
onUpdate: (callback: (data: unknown) => void) => () => void;
}
const myPluginAPI: MyPluginAPI = {
performAction: (data) => ipcRenderer.invoke("my-plugin-action", data),
closeWindow: () => ipcRenderer.send("my-plugin-close"),
onUpdate: (callback) => {
const handler = (_event: unknown, data: unknown) => callback(data);
ipcRenderer.on("my-plugin-update", handler);
return () => ipcRenderer.removeListener("my-plugin-update", handler);
},
};
contextBridge.exposeInMainWorld("myPluginAPI", myPluginAPI);
Always return cleanup functions from event subscriptions to prevent listener accumulation.
Event-Driven Communicationβ
Monitor host application events using ipcMain listeners:
import { ipcMain } from "electron";
export class EventMonitor {
public initialize(): void {
ipcMain.on("start-capture-screen", () => this.onCaptureStart());
ipcMain.on("stop-capture-screen", () => this.onCaptureStop());
}
public dispose(): void {
ipcMain.removeAllListeners("start-capture-screen");
ipcMain.removeAllListeners("stop-capture-screen");
}
}
Timer and Interval Managementβ
Encapsulate timer state for correct disposal:
export class Timer {
private intervalId: NodeJS.Timeout | null = null;
public start(callback: () => void, interval: number): void {
if (this.intervalId) return;
this.intervalId = setInterval(callback, interval);
}
public stop(): void {
if (this.intervalId) {
clearInterval(this.intervalId);
this.intervalId = null;
}
}
public dispose(): void {
this.stop();
}
}
Build Configurationβ
All plugin code is bundled using Webpack with ts-loader. Target is node with electron as external. The libraryTarget is umd.
const path = require("path");
const CopyWebpackPlugin = require("copy-webpack-plugin");
module.exports = {
mode: "production",
entry: {
index: path.resolve(__dirname, "index.ts"),
preload: path.resolve(__dirname, "preload.ts"),
},
output: {
path: path.resolve(__dirname, "build"),
filename: "[name].bundle.js",
libraryTarget: "umd",
},
module: {
rules: [{ test: /\.ts$/, exclude: /node_modules/, use: "ts-loader" }],
},
plugins: [
new CopyWebpackPlugin({
patterns: [
{ from: "manifest.json", to: "" },
{ from: "package.json", to: "" },
{ from: "ui", to: "ui" },
{ from: "assets", to: "assets" },
],
}),
],
resolve: { extensions: [".ts", ".js"] },
externals: ["electron"],
target: "node",
};
Dependency Managementβ
Bundled Dependencies: Place in node_modules/ or native_modules/ subdirectory. The PluginManager automatically renames native_modules/ to node_modules/ during installation.
npm Strategy-Resolved: When installed via npm, package.json dependencies are resolved automatically.
Publishing to the Marketplaceβ
- Build: Execute
npm run buildand compressbuild/into a ZIP - Navigate: Open the upload dialog in Gauzy Desktop UI
- Complete the form: Basic info, version details, source descriptors, subscription plans
- Submit: The artifact uploads with real-time progress reporting
For npm-based publication:
{
"name": "@your-scope/my-plugin",
"version": "1.0.0",
"files": ["build/**/*"],
"scripts": { "prepublishOnly": "npm run build" }
}
npm login
npm publish --access public