# WARNING WIP (HELP WANTED) These docs are a first draft and at many places plain wrong. I think the best way to figure out how to write a plugin is to check aut the example plugins: https://github.com/johannesjo/super-productivity/tree/master/packages/plugin-dev/ https://github.com/johannesjo/super-productivity/tree/master/packages/plugin-dev/yesterday-tasks-plugin It is not super complicated, I think :) --- # Super Productivity Plugin Development Guide This guide covers everything you need to know about creating plugins for Super Productivity. ## Table of Contents - [Quick Start](#quick-start) - [Plugin Manifest](#plugin-manifest) - [Plugin Types](#plugin-types) - [Available API Methods](#available-api-methods) - [Best Practices](#best-practices) - [Security Considerations](#security-considerations) - [Testing Your Plugin](#testing-your-plugin) ## Quick Start ### 1. Use the Plugin Boilerplate or copy example plugin https://github.com/johannesjo/super-productivity/tree/master/packages/plugin-dev/boilerplate-solid-js https://github.com/johannesjo/super-productivity/tree/master/packages/plugin-dev/yesterday-tasks-plugin ### 2. Basic Plugin Structure ``` my-plugin/ ├── manifest.json # Plugin metadata (required) ├── plugin.js # Main plugin code (optional) ├── index.html # UI interface (optional) └── icon.svg # Plugin icon (optional) ``` ### 3. Minimal Example **manifest.json:** ```json { "id": "hello-world", "name": "Hello World Plugin", "version": "1.0.0", "description": "My first Super Productivity plugin", "manifestVersion": 1, "minSupVersion": "8.0.0" } ``` **plugin.js:** ```javascript console.log('Hello World plugin loaded!'); // Show a notification PluginAPI.showSnack({ msg: 'Hello from my plugin!', type: 'SUCCESS', }); // Register a header button PluginAPI.registerHeaderButton({ label: 'Hello', icon: 'waving_hand', onClick: () => { PluginAPI.showSnack({ msg: 'Button clicked!', type: 'INFO', }); }, }); ``` ## Plugin Manifest The `manifest.json` file is required for all plugins and defines the plugin's metadata and configuration. ### Manifest Fields | Field | Type | Required | Description | | ----------------- | -------- | -------- | ---------------------------------------------------------- | | `id` | string | ✓ | Unique identifier for your plugin (use kebab-case) | | `name` | string | ✓ | Display name shown to users | | `version` | string | ✓ | Semantic version (e.g., "1.0.0") | | `description` | string | ✓ | Brief description of what your plugin does | | `manifestVersion` | number | ✓ | Currently must be `1` | | `minSupVersion` | string | ✓ | Minimum Super Productivity version required | | `author` | string | | Plugin author name | | `homepage` | string | | Plugin website or repository URL | | `icon` | string | | Path to icon file (SVG recommended) | | `iFrame` | boolean | | Whether plugin uses iframe UI (default: false) | | `sidePanel` | boolean | | Show plugin in side panel (default: false) | | `permissions` | string[] | | The permissions the plugin needs (e.g., ["nodeExecution"]) | | `hooks` | string[] | | App events to listen to | ### Complete Manifest Example ```json { "id": "my-advanced-plugin", "name": "My Advanced Plugin", "version": "2.1.0", "description": "An advanced plugin with UI and hooks", "manifestVersion": 1, "minSupVersion": "8.0.0", "author": "John Doe", "homepage": "https://github.com/johndoe/my-plugin", "icon": "icon.svg", "iFrame": true, "sidePanel": false, "permissions": ["nodeExecution"], "hooks": ["taskComplete", "taskUpdate", "currentTaskChange"] } ``` ## Plugin Types ### 1. JavaScript Plugins (`plugin.js`) Pure JavaScript plugins that run in a sandboxed environment with full API access. **Use when:** - You need to register UI components (buttons, menu items) - You want to listen to app hooks/events - You need programmatic interaction with tasks/projects **Example:** ```javascript // Register multiple UI elements PluginAPI.registerHeaderButton({ label: 'My Button', icon: 'star', onClick: async () => { const tasks = await PluginAPI.getTasks(); console.log(`You have ${tasks.length} tasks`); }, }); PluginAPI.registerHook(PluginAPI.Hooks.TASK_COMPLETE, (taskId) => { console.log(`Task ${taskId} completed!`); }); ``` ### 2. HTML/Iframe Plugins (`index.html`) Plugins that render custom UI in a sandboxed iframe. **Use when:** - You need custom UI/visualizations - You want to display charts, forms, or complex interfaces - You prefer working with HTML/CSS **Important:** When using iframes, you must inline all CSS and JavaScript directly in the HTML file. External stylesheets and scripts are blocked for security reasons. **Example index.html:** ```html My Plugin UI

My Plugin

``` ### 3. Hybrid Plugins Combine both `plugin.js` and `index.html` for maximum flexibility. **plugin.js:** ```javascript // Register UI elements and hooks PluginAPI.registerSidePanelButton({ label: 'Open My Plugin', icon: 'dashboard', onClick: () => { PluginAPI.showIndexHtmlAsView(); }, }); // Listen for events and update iframe PluginAPI.registerHook(PluginAPI.Hooks.TASK_UPDATE, () => { // Notify iframe about task update // (iframe will receive this via PluginAPI events) }); ``` ## Available API Methods ### Data Operations #### Tasks - `getTasks()` - Get all active tasks - `getArchivedTasks()` - Get archived tasks - `getCurrentContextTasks()` - Get tasks in current context - `addTask(task)` - Create a new task - `updateTask(taskId, updates)` - Update existing task #### Projects - `getAllProjects()` - Get all projects - `addProject(project)` - Create new project - `updateProject(projectId, updates)` - Update project #### Tags - `getAllTags()` - Get all tags - `addTag(tag)` - Create new tag - `updateTag(tagId, updates)` - Update tag ### UI Operations #### Notifications ```javascript // Show snackbar notification PluginAPI.showSnack({ msg: 'Operation completed!', type: 'SUCCESS', // SUCCESS, ERROR, INFO, WARNING ico: 'check', // Optional Material icon actionStr: 'Undo', // Optional action button actionFn: () => console.log('Undo clicked'), }); // System notification PluginAPI.notify({ title: 'Task Complete', body: 'Great job!', ico: 'done', }); ``` #### Dialogs ```javascript // Open a dialog const result = await PluginAPI.openDialog({ title: 'Confirm Action', content: 'Are you sure?', okBtnLabel: 'Yes', cancelBtnLabel: 'No', }); ``` ### Registration Methods (plugin.js only) #### Header Button ```javascript PluginAPI.registerHeaderButton({ id: 'my-header-btn', // Optional unique ID label: 'Click Me', icon: 'star', // Material icon name onClick: () => { console.log('Header button clicked'); }, }); ``` #### Menu Entry ```javascript PluginAPI.registerMenuEntry({ label: 'My Plugin Action', icon: 'extension', onClick: () => { console.log('Menu item clicked'); }, }); ``` #### Side Panel Button ```javascript PluginAPI.registerSidePanelButton({ label: 'My Panel', icon: 'dashboard', onClick: () => { PluginAPI.showIndexHtmlAsView(); }, }); ``` #### Keyboard Shortcut ```javascript PluginAPI.registerShortcut({ keys: 'ctrl+shift+p', label: 'My Plugin Shortcut', action: () => { console.log('Shortcut triggered'); }, }); ``` #### Hooks ```javascript // Available hooks const hooks = { TASK_COMPLETE: 'taskComplete', TASK_UPDATE: 'taskUpdate', TASK_DELETE: 'taskDelete', CURRENT_TASK_CHANGE: 'currentTaskChange', FINISH_DAY: 'finishDay', LANGUAGE_CHANGE: 'languageChange', PERSISTED_DATA_UPDATE: 'persistedDataUpdate', ACTION: 'action', }; // Register hook listener PluginAPI.registerHook(PluginAPI.Hooks.TASK_COMPLETE, (taskId) => { console.log(`Task ${taskId} completed!`); }); // Listen to Redux actions PluginAPI.registerHook(PluginAPI.Hooks.ACTION, (action) => { if (action.type === 'ADD_TASK_SUCCESS') { console.log('New task added:', action.payload); } }); ``` ### Data Persistence ```javascript // Save plugin data await PluginAPI.persistDataSynced('myKey', { count: 42 }); // Load saved data const data = await PluginAPI.loadSyncedData('myKey'); console.log(data); // { count: 42 } ``` ## Best Practices ### 1. Performance - **Lazy load resources**: Don't load everything on plugin initialization - **Debounce expensive operations**: Use throttling for frequent events - **Clean up resources**: Remove event listeners when appropriate ### 2. Error Handling Always wrap async operations in try-catch blocks: ```javascript async function loadData() { try { const tasks = await PluginAPI.getTasks(); // Process tasks } catch (error) { console.error('Failed to load tasks:', error); PluginAPI.showSnack({ msg: 'Error loading tasks', type: 'ERROR', }); } } ``` ### 3. User Experience - **Provide feedback**: Show loading states and confirmations - **Be non-intrusive**: Don't spam notifications - **Follow the app's design**: Use Material icons and consistent styling - **Respect user preferences**: Check dark mode, language settings ### 4. Security - **Validate inputs**: Never trust user input - **Avoid eval()**: Use safe JSON parsing instead - **Sanitize HTML**: If displaying user content - **Request minimal permissions**: Only what you need ### 5. Iframe Development When developing iframe plugins: 1. **Inline everything**: CSS and JavaScript must be in the HTML file 2. **Wait for API ready**: Listen for the `PluginApiReady` event 3. **Handle errors gracefully**: Iframes can fail to load 4. **Keep it lightweight**: Iframes add overhead ```html
``` ## Security Considerations ### Sandboxing - JavaScript plugins run in isolated VM contexts - Iframe plugins run in sandboxed iframes with restricted permissions - No access to Node.js APIs unless explicitly granted - No access to file system or network unless through API ### API Restrictions In iframe context, these methods are NOT available: - `registerHeaderButton()` - `registerMenuEntry()` - `registerSidePanelButton()` - `registerShortcut()` - `registerHook()` - `execNodeScript()` ### Content Security Policy - External scripts/styles are blocked in iframes - Only same-origin resources are allowed - Inline scripts must be within the HTML file ## Testing Your Plugin ### 1. Local Development 1. Enable Developer Mode in Super Productivity settings 2. Use "Load Plugin from Folder" to test your plugin 3. Open DevTools (F12) to see console logs 4. Use the API Test Plugin as reference ### 2. Debugging Tips ```javascript // Add debug logging const DEBUG = true; function log(...args) { if (DEBUG) { console.log('[MyPlugin]', ...args); } } // Test API methods async function testAPI() { log('Testing getTasks...'); const tasks = await PluginAPI.getTasks(); log('Tasks:', tasks); log('Testing showSnack...'); PluginAPI.showSnack({ msg: 'API test successful!', type: 'SUCCESS', }); } ``` ### 3. Common Issues **Plugin not loading:** - Check manifest.json syntax - Verify minSupVersion compatibility - Look for errors in console **API methods failing:** - Ensure PluginAPI is ready before use - Check if method is available in current context - Verify permissions in manifest **Iframe not displaying:** - Check that all resources are inlined - Verify no external dependencies - Look for CSP violations in console ## Examples ### Task Reporter Plugin ```javascript // plugin.js let reportInterval; PluginAPI.registerHeaderButton({ label: 'Start Report', icon: 'assessment', onClick: () => { if (reportInterval) { clearInterval(reportInterval); reportInterval = null; PluginAPI.showSnack({ msg: 'Reporting stopped', type: 'INFO' }); } else { reportInterval = setInterval(generateReport, 3600000); // Every hour PluginAPI.showSnack({ msg: 'Reporting started', type: 'SUCCESS' }); } }, }); async function generateReport() { const tasks = await PluginAPI.getCurrentContextTasks(); const completed = tasks.filter((t) => t.isDone).length; const total = tasks.length; PluginAPI.notify({ title: 'Hourly Report', body: `Completed ${completed} of ${total} tasks`, ico: 'pie_chart', }); } ``` ### Custom Dashboard Plugin ```html

Task Dashboard

-
Total Tasks
-
Completed
-
Projects
-
Tags
``` ## Resources - **Plugin API Types**: [@super-productivity/plugin-api](https://www.npmjs.com/package/@super-productivity/plugin-api) - **Plugin Boilerplate**: [GitHub Repository](https://github.com/johannesjo/super-productivity-plugin-boilerplate) - **Example Plugins**: Check the `src/app/features/plugin/plugins` directory - **Community Plugins**: [Awesome Super Productivity](https://github.com/johannesjo/super-productivity#plugins) ## Contributing If you create a useful plugin, consider: 1. Publishing it to npm 2. Submitting a PR to add it to the community plugins list 3. Sharing it in the Super Productivity discussions Happy plugin development! 🚀