Developers · Plugin API v1
Build a plugin in two files.
A Venora plugin is a plugin.json manifest and one ES module. This page is the guided tour. The API v1 specification in the repository is the contract, and you can write a plugin from it without reading Venora's source.
01 Quick start
Structure
Put the files in a folder and ZIP it. plugin.json can be at the root of the ZIP or inside one top-level folder. Install it from Settings → Plugins → Add plugin.
uiplugin.json
{
"id": "greeter",
"name": "Greeter",
"version": "1.0.0",
"author": "Your name",
"description": "Says hello when Fermo opens.",
"main": "index.js",
"permissions": ["storage", "ui"],
"compatibility": {"apiVersion": 1, "minVenoraVersion": "0.1.0"},
"settings": [
{"key": "greeting", "name": "Greeting", "type": "string", "default": "Hello!"}
],
"changelog": [
{"version": "1.0.0", "added": ["Says hello."]}
]
}
id, name and version are required. The id is permanent: updates, settings and storage are keyed by it. version follows semantic versioning, and an update must be higher. Put apiVersion inside compatibility. A top-level one is rejected, so it can't be silently ignored.
index.js
export default async (venora) => {
const greeting = await venora.settings.get({key: "greeting"});
await venora.ui.toast({message: greeting});
const unsubscribe = await venora.on("settings.changed", ({key, value}) => {
if (key === "greeting") venora.ui.toast({message: value});
});
return () => {
// Cleanup: clear your own timers. Venora removes anything it added to
// the page for this plugin, and ends its subscriptions, automatically.
unsubscribe();
};
};
02 The API
The venora object
Your default export is called with one argument, venora. It is created fresh each time the plugin starts, frozen, and not shared with any other plugin.
- One argument. Every method takes at most one plain object. Unknown fields are ignored, including any
pluginId. - Always a promise. Every call crosses the bridge, so every method returns a promise that resolves to a copy of plain data.
- One method, one capability.
venora.ui.toast(p)is exactlyvenora.request("ui.toast", p), with the same checks.
| Method | Permissions | Input → result | Own rate limit |
|---|---|---|---|
venora.ui.toast | ui | {message, durationMs?} → null. Plain text, up to 200 characters, shown for 1–10 s. | 5, then 1 / 2 s |
venora.settings.get | storage | {key} → the saved value, else the declared default, else null | none |
venora.storage.get | storage | {key} → the stored value or null | none |
venora.storage.set | storage | {key, value} → null | 60, then 20/s |
venora.storage.remove | storage | {key} → null | 60, then 20/s |
venora.storage.clear | storage | no input → null; clears only your keys | 60, then 20/s |
venora.mentions.showAvatar | ui account network | {mentionId} → {shown}. Venora looks the avatar up; you never see the URL. | 100, then 50/s |
venora.mentions.setAvatarSize | ui | {size}, 12–32 px → null | 10, then 2/s |
venora.on | per event | (event, listener) → an unsubscribe function | 20, then 2/s |
The object also has pluginId, apiVersion (1), and request(capability, payload), the low-level form of every method.
03 Events
Listening with venora.on
| Event | Permissions | Payload | Sent when |
|---|---|---|---|
settings.changed | storage | {key, value} | The user changes one of your settings while your plugin runs. Only you receive it. |
mentions.added | ui | {mentions: [{mentionId, userId}]} | Mentions appear on the page. New subscribers also get the ones already there, once each. |
Delivery is at most once, in order, with no retries. Events that happen while you aren't subscribed aren't queued. Listeners can't cancel anything, and an exception in one is reported without affecting the others. Internal Venora events are never visible to plugins.
04 Permissions
Declare what you need, nothing more
A capability needs all of its permissions. Without one, the call rejects with permission_denied before your payload is even read. Permissions are fixed per installed version: there are no runtime prompts and no optional permissions. Adding a sensitive permission in an update means the user sees the warning again.
storagesettings and storageuitoasts, CSS, mentionsnetworksensitive · avatar lookup onlyaccountsensitive · avatar lookup only05 Settings & storage
The user's choices, and your own data
Settings
- Declared in
plugin.json:boolean,string,number(withmin,max,step) orselect - Written only by the user, in a dialog Venora draws. There is no
settings.set. - Read them at start, then follow
settings.changed
Storage
- Keys of 1–100 characters; values are plain data
- 64 KiB per value, 1 MiB and 1,000 keys per plugin, else
quota_exceeded - Atomic, applied in call order, kept across reloads and updates, deleted on uninstall
Storage lives in the browser profile and is shared by every Fermo account used there, so don't assume data belongs to the current account. Each tab runs its own instance, and simultaneous writes from two tabs can lose one tab's change.
06 UI & CSS
Changing how Fermo looks
Name a stylesheet in "css" and declare ui. Venora applies it while your plugin runs and removes it when it stops. The CSS isn't applied if it contains url(, @import, image-set(, a backslash (among a few others), a quoted string other than "", or @counter-style, because those can load resources or write text onto the page. It also has to be under 131,072 characters. Your plugin still runs, and the reason is logged.
Plugin CSS applies to Fermo's page only. It can't reach Venora's dialogs, notices or Plugins page. There's no API for the DOM, dialogs or rendering HTML.
07 Lifecycle
When your code runs
startingmodule loads; default export runs; API availablerunningexport returned or resolvedstoppingcleanup runs; API calls getruntime_stoppedstoppedframe removed; page additions removed
- Plugins start on
/channels/pages and stop when Fermo navigates elsewhere. The same tab can start and stop your plugin many times, and each start is a fresh module instance. - Startup has 10 seconds. Start long work without awaiting it. Cleanup has 2 seconds.
- Cleanup is not guaranteed: it doesn't run on reload, tab close, or when Venora terminates a plugin. Save data when it changes, not on the way out.
- A plugin that fails to start shows
failedwith its error on its card and isn't retried until the next page load or navigation back to a channel.
08 Environment
What's available, and what isn't
Available
- The
venoraobject - Standard JavaScript built-ins,
consoleand timers crypto.getRandomValues,TextEncoder/TextDecoder,Intl
Blocked on purpose
fetch, XHR, WebSocket and loading any resourcewindow,documentand navigationlocalStorage, IndexedDB, cookies andchrome.*eval,new Functionand relative imports (bundle into one file)alert,confirm,promptand popups
09 Errors & limits
Handle error.code, not the message
Failed calls reject with an Error carrying a stable code. Messages are for humans and can change. Treat an unknown code as a general failure, because new codes may be added.
permission_denieda required permission isn't declaredinvalid_payloadwrong type, out of range, or an undeclared setting keyrate_limitedover a request limit; nothing was donequota_exceededa storage limit; nothing was writtentimeoutno answer in 30 s; it may still have happenedruntime_stoppedcalled during or after cleanupunknown_capabilitya typo, or a newer Venora's capabilityunknown_eventnot a public eventsensitive_data_blockedthe result contained a session tokencapability_failedan internal failure; details stay in Venorainvalid_messagethe request couldn't be readEach plugin also has request, event and message limits, a watchdog, and storage and install size caps. A plugin that keeps exceeding them is stopped. The full table is on the security page.
10 Versioning
What API v1 promises
Within version 1, Venora may add capabilities, events, optional fields and error codes, and may raise limits. It won't remove or rename anything, add a permission to an existing capability, or lower a limit. Those need API version 2. The one exception is a breaking change needed to fix a vulnerability, which must be as small as possible and documented in the release notes.
When you use something added in a later Venora release, set minVenoraVersion so older releases refuse to install your plugin instead of failing at runtime.
11 Reference
Documentation in the repository
plugin.json field, and ZIP packaging
plugin-permissions.mdPermissions and the capabilities they unlock
plugin-lifecycle.mdWhen code runs, cleanup, install, update and uninstall
plugin-settings-storage.mdSettings declarations and private storage
plugin-security.mdThe mandatory security requirements
runtime-architecture.mdHow the sandbox and bridge are built
CONTRIBUTING.mdWorking on Venora itself, and changing the API