docs: update plugin development guide

This commit is contained in:
Johannes Millan 2025-07-03 12:49:59 +02:00
parent 63f9527080
commit 0a55fa2823

View file

@ -1,17 +1,20 @@
# 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.
This is a comprehensive documentation of the Super Productivity Plugin System. This guide covers everything you need to know about creating plugins for Super Productivity.
These docs might not always be perfectly up to date. You find the latest typescript interfaces here:
[types.ts](../packages/plugin-api/src/types.ts)
Personally I think the best way to figure out how to write a plugin is to check out the example plugins:
- [yesterday-tasks-plugin](../packages/plugin-dev/yesterday-tasks-plugin)
- [procrastination-buster](../packages/plugin-dev/procrastination-buster)
- [api-test-plugin](../packages/plugin-dev/api-test-plugin)
If you want to build a sophisticated UI there is a boilerplate available for solidjs:
[boilerplate-solid-js](../packages/plugin-dev/boilerplate-solid-js)
---
## Table of Contents
@ -25,22 +28,17 @@ This guide covers everything you need to know about creating plugins for Super P
## 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
### 1. Basic Plugin Structure
```
my-plugin/
├── manifest.json # Plugin metadata (required)
├── plugin.js # Main plugin code (optional)
├── index.html # UI interface (optional)
├── plugin.js # Main plugin code that is launched when activated and when Super Productivity starts
├── index.html # UI interface (optional) => requires iFrame:true in manifest
└── icon.svg # Plugin icon (optional)
```
### 3. Minimal Example
### 2. Minimal Example
**manifest.json:**
@ -51,7 +49,7 @@ my-plugin/
"version": "1.0.0",
"description": "My first Super Productivity plugin",
"manifestVersion": 1,
"minSupVersion": "8.0.0"
"minSupVersion": "14.0.0"
}
```
@ -85,21 +83,21 @@ The `manifest.json` file is required for all plugins and defines the plugin's me
### 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 |
| 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), requires `iFrame:true` |
| `permissions` | string[] | | The permissions the plugin needs (e.g., ["nodeExecution"]) |
| `hooks` | string[] | | App events to listen to |
### Complete Manifest Example
@ -110,7 +108,7 @@ The `manifest.json` file is required for all plugins and defines the plugin's me
"version": "2.1.0",
"description": "An advanced plugin with UI and hooks",
"manifestVersion": 1,
"minSupVersion": "8.0.0",
"minSupVersion": "14.0.2",
"author": "John Doe",
"homepage": "https://github.com/johndoe/my-plugin",
"icon": "icon.svg",
@ -129,7 +127,8 @@ Pure JavaScript plugins that run in a sandboxed environment with full API access
**Use when:**
- You need to register UI components (buttons, menu items)
- For setup background stuff that is to be executed even when the plugin ui (iFrame) is not shown
- For registering and handling keyboard shortcuts
- You want to listen to app hooks/events
- You need programmatic interaction with tasks/projects
@ -159,7 +158,6 @@ Plugins that render custom UI in a sandboxed iframe.
- 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.
@ -244,39 +242,11 @@ Plugins that render custom UI in a sandboxed iframe.
});
}
});
// Error handling
window.addEventListener('error', (event) => {
console.error('Plugin error:', event.error);
});
</script>
</body>
</html>
```
### 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
@ -416,6 +386,8 @@ PluginAPI.registerHook(PluginAPI.Hooks.ACTION, (action) => {
### Data Persistence
You can persist data that will also be synced vai the `persistDataSynced` and `loadSyncedData` APIs. For local storage I recommend using `localStorage`.
```javascript
// Save plugin data
await PluginAPI.persistDataSynced('myKey', { count: 42 });
@ -430,50 +402,25 @@ console.log(data); // { count: 42 }
### 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
- **Be responsive with using resources**: Avoid heavy operations and don't save excessive amounts of data.
- **Keep it lightweight**: Super Productivity is not the only app on the users system and your plugin is not the only plugin.
### 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
### 2. 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
- **Follow the app's design**: Use the injected theme variables and try to keep styles minimal.
- **Respect user preferences**: Check dark mode, and language settings (if possible or stick to english if not)
### 4. Security
### 3. 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
### 4. Don't spam the logs
When developing iframe plugins:
`console.logs` should be kept to a minimum.
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
<!-- Good: Everything inlined -->
@ -499,8 +446,7 @@ When developing iframe plugins:
- 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
- No access to file system unless through API
### API Restrictions
@ -523,10 +469,9 @@ In iframe context, these methods are NOT available:
### 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
1. Use "Load Plugin from Folder" to test your plugin
2. Open DevTools (F12 or Ctrl+Shift+i) to see console logs
3. Use the API Test Plugin as reference
### 2. Debugging Tips
@ -564,7 +509,6 @@ async function testAPI() {
**API methods failing:**
- Ensure PluginAPI is ready before use
- Check if method is available in current context
- Verify permissions in manifest
@ -574,172 +518,18 @@ async function testAPI() {
- 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
<!-- index.html -->
<!DOCTYPE html>
<html>
<head>
<style>
body {
font-family: Arial, sans-serif;
margin: 0;
padding: 20px;
background: #f0f0f0;
}
.dashboard {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 20px;
}
.stat-card {
background: white;
padding: 20px;
border-radius: 8px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
text-align: center;
}
.stat-value {
font-size: 36px;
font-weight: bold;
color: #2196f3;
}
.stat-label {
color: #666;
margin-top: 8px;
}
</style>
</head>
<body>
<h1>Task Dashboard</h1>
<div
class="dashboard"
id="dashboard"
>
<div class="stat-card">
<div
class="stat-value"
id="totalTasks"
>
-
</div>
<div class="stat-label">Total Tasks</div>
</div>
<div class="stat-card">
<div
class="stat-value"
id="completedTasks"
>
-
</div>
<div class="stat-label">Completed</div>
</div>
<div class="stat-card">
<div
class="stat-value"
id="totalProjects"
>
-
</div>
<div class="stat-label">Projects</div>
</div>
<div class="stat-card">
<div
class="stat-value"
id="totalTags"
>
-
</div>
<div class="stat-label">Tags</div>
</div>
</div>
<script>
window.addEventListener('PluginApiReady', async () => {
async function updateDashboard() {
try {
const [tasks, projects, tags] = await Promise.all([
PluginAPI.getTasks(),
PluginAPI.getAllProjects(),
PluginAPI.getAllTags(),
]);
document.getElementById('totalTasks').textContent = tasks.length;
document.getElementById('completedTasks').textContent = tasks.filter(
(t) => t.isDone,
).length;
document.getElementById('totalProjects').textContent = projects.length;
document.getElementById('totalTags').textContent = tags.length;
} catch (error) {
console.error('Dashboard update failed:', error);
}
}
// Initial load
updateDashboard();
// Refresh every 30 seconds
setInterval(updateDashboard, 30000);
});
</script>
</body>
</html>
```
## 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)
- **Plugin Boilerplate**: [boilerplate-solid-js](../packages/plugin-dev/boilerplate-solid-js)
- **Example Plugins**: [plugin-dev](../packages/plugin-dev)
- **Community Plugins**: Coming Soon!
## 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
1. Posting on reddit or GitHub discussions about it
2. Submitting a PR to add it to the community plugins list (coming soon)
Happy plugin development! 🚀