Pular para o conteΓΊdo principal

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​

TemplateBundle SizeBest Suited For
plugin-template-html (HTML/CSS/JS)~60 KBSimple settings pages, minimal interactivity
plugin-template-react (React 18+)~200 KBComponent-oriented UIs, reactive state management
plugin-template-angular (Angular 19+)~350 KBComplex 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: false to prevent premature display
  • Call show() explicitly within activate()
  • 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);
tip

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​

  1. Build: Execute npm run build and compress build/ into a ZIP
  2. Navigate: Open the upload dialog in Gauzy Desktop UI
  3. Complete the form: Basic info, version details, source descriptors, subscription plans
  4. 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