A reference template for creating feedBack plugins.
This template is provided "AS-IS" without any warranty of any kind, express or implied. No warranty is given that this template is correct, complete, safe, or fit for any particular purpose.
You MUST read and follow the official Plugin Specification before creating a plugin. This template is an example that demonstrates some common patterns, but:
- The specification is the authoritative reference. This template may not cover all requirements.
- You are responsible for ensuring your plugin complies with the specification.
- Compliance with this template does not guarantee compliance with the specification.
- If this template conflicts with the specification, the specification takes precedence.
Liability: The authors, contributors, and distributors of this template disclaim all liability for any damages, losses, or consequences arising from the use or inability to use this template, including but not limited to data loss, system failures, security vulnerabilities, or any other issues related to plugin development or deployment.
Read the spec. This is your responsibility.
-
Copy the template: Duplicate the
my-plugin/directory and rename it to your plugin'sid.cp -r my-plugin your-plugin
-
Update the manifest: Edit
your-plugin/plugin.json:- Change
"id"to match your folder name (e.g.,"your-plugin") - Update
name,version,description,type,icon - Update the hard-coded
PLUGIN_IDinscreen.js,settings.html, androutes.py(including thenav.screenvalue inplugin.json) so the screen lookup and/api/plugins/<id>/...routes follow the new id
- Change
-
Customize the plugin: Edit the files in your plugin directory:
screen.html— Client screen markup (the counter and settings display)screen.js— Client-side screen logicsettings.html— Settings panel markup and behaviorroutes.py— Server-side logic and persistenceassets/plugin.css— Styling
-
Drop into feedBack: Copy your plugin folder into one of the Host's plugin directories — the in-tree
plugins/directory, or a directory named by theFEEDBACK_PLUGINS_DIRenvironment variable (spec §5.1) — or install it through the in-app Plugin Manager. -
Reload plugins: Restart feedBack or use the plugin manager to reload. Your plugin should appear in the plugin list.
The automated checks (the same ones custom-checks.yml runs in CI):
pip install -r requirements-dev.txt # test-only deps: fastapi, httpx, pytest
pytest -qThe suite covers the manifest/screen mount contract and the settings routes (GET defaults, POST/persistence, invalid and unknown values, malformed JSON, and the 16 KiB body limit).
To see the example live, install my-plugin/ into feedBack (step 4 above), reload, and check the "My Plugin" sidebar entry:
- Click Increment — the counter climbs.
- Open the plugin's settings panel, change the color, click Save Settings, then reopen the screen — the "Active color" display reflects the saved value, served from
/api/plugins/my-plugin/settings.
A complete, working plugin that demonstrates:
- A client screen (
screen.htmlmarkup +screen.jslogic) with lifecycle management - Settings persistence (HTML form + server routes)
- Server routes (
routes.py) for reading/writing settings - Styling (
assets/plugin.css) scoped to the plugin - Documentation (
README.md) with common tasks and tips
Each file is heavily commented and shows best practices from the Plugin Specification.
The specification makes the directory name the plugin's identity and requires it to equal the manifest id exactly (§5.2), including case — it calls a mismatch the most common discovery failure.
tuner/ ✅ directory name == id
├── plugin.json → {"id": "tuner", ...}
└── ...
Tuner/ ❌ spec-invalid (name doesn't match id)
├── plugin.json → {"id": "tuner", ...}
└── ...
The shipped Host is currently more permissive — it registers a plugin by its manifest id regardless of the folder name, and the directory name only affects bundled duplicate resolution — but that leniency is not a documented contract, so match them anyway. AGENTS.md spells out this and the other spec-vs-Host differences, along with the copy/rename checklist.
Settings are stored server-side in the Host's config directory (context["config_dir"]) as <id>.json, not in browser storage. Always fetch/post through your routes:
// Load
const res = await fetch(`/api/plugins/${PLUGIN_ID}/settings`);
const settings = await res.json();
// Save
await fetch(`/api/plugins/${PLUGIN_ID}/settings`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ key: "value" }),
});Because all plugins share one document and one window, prefix everything with your plugin id:
- DOM ids/classes:
.my-plugin,#plugin-my-plugin windowglobals:window.my_plugin_statelocalStoragekeys:my_plugin_config- Route paths:
/api/plugins/my_plugin/...
Namespace collisions silently clobber each other, so be explicit.
The Host may execute your screen.js more than once (e.g., during plugin reloads). Guard against duplicate listeners:
if (!window.__my_plugin_setup) {
window.__my_plugin_setup = true;
// Set up listeners, timers, observers — only once
}See the template's screen.js for the pattern.
- Plugin Specification — The normative contract. Start here if something doesn't work.
- Best Practices Guide — Non-normative advice on building good plugins.
- Full Example Plugin — Another worked example in the spec repo.
AGENTS.md— The development guide for this repo: the contract's spec and Host sources with the revisions this template was verified against, the spec-vs-Host differences, the copy/rename checklist, and the verification and completion criteria.
- Check the folder name matches the manifest
id(case-sensitive). - Run the reference validator:
python tools/validate.py <plugin-folder>(if cloned from the spec repo). - Check the Host's logs for import errors in
routes.pyorscriptparsing errors.
- Check browser DevTools Network tab for errors on POST to
/api/plugins/<id>/settings. - Verify the Host can write to its config directory.
- Check the Host's logs for route errors in your
routes.py.
- Verify the manifest
styleskey points to the correct CSS file. - Check that selectors are scoped to your plugin's class (not
.screenor global). - DevTools may show the Host's styles overriding yours — use higher specificity if needed.
This template and all example code are licensed under the GNU Affero General Public License v3.0 or later (AGPL-3.0-or-later). See the LICENSE file for full details.
When you create a plugin from this template:
-
You own your plugin. You are free to choose any license for your plugin code. The template is provided under AGPL-3.0-or-later, but your derivative work can use a different license if you prefer.
-
Recommended licenses:
- AGPL-3.0-or-later — Ensures derivatives remain free and open. Good for plugins you want to keep community-driven.
- MIT — Permissive, widely used, minimal requirements. Good for permissive distribution.
- Apache-2.0 — Permissive with explicit patent grant. Good for corporate environments.
- GPL-3.0-or-later — Copyleft without the network clause. Simpler than AGPL for non-network plugins.
-
Include a LICENSE file in your plugin's directory with your chosen license text.
-
Add SPDX headers to each source file (see the template files for examples).
-
Document your license in your plugin's README.
The feedBack project itself uses AGPL-3.0-or-later to ensure the platform and its ecosystem remain free and community-driven. Your plugin's license is independent of this choice.