Build a plugin
Begin with one useful action. Keep compatibility, authority, and runtime behavior visible as you add capabilities.
Get the template and types
The public plugin repository contains a template, declarations, registry, and package checks. For unreleased APIs, compare it with the current source contract and use a matching development build. The latest published app may be older than this documentation.
Use Bun for the repository scripts. Copy template/ to plugins/<your-plugin-id>/ and keep the directory name equal to the manifest ID.
A minimal command
This example registers a command during activation and performs its UI effect only when the user runs it.
{
"id": "hello-reader",
"name": "Hello Reader",
"version": "0.1.0",
"schemaVersion": 1,
"main": "main.js",
"requires": {
"contributions": { "commands": "^1.1.0" },
"services": { "ui": "^1.17.0" }
}
}export default {
activate(ctx) {
ctx.contributions.commands.register({
id: "hello",
title: "Say hello",
run: () => ctx.services.ui.showToast("Hello, reader!"),
});
},
};Compile the entry module to a self-contained main.js. The manifest above intentionally requires the current documented versions; use older ranges only after checking and testing those contracts.
Add the smallest useful authority
Use the Explorer to find a method and copy its starting manifest fragment. A fragment declares a capability; complete the grants needed by the particular operation.
- Reading library data requires
library:read; changing it requireslibrary:write. - Settings use exact
settingsAccesspaths and operations. - Arbitrary HTTP needs
service:networkplus allowednetworkAccess.origins. - A reader-related method in a permission-free UI service may still need
reading:readorreading:write. - Book grants are selected through host consent and exposed in
ctx.grants.book; a manifest cannot give itself access to another book.
Inspect ctx.capabilities and handle absent optional namespaces. Keep ReadAware-owned data in its domains; plugin storage is for your own records, settings, and checkpoints.
Work with observations and cancellation
Release handles when you no longer need them. For supported calls, pass an AbortSignal and await the actual outcome. Cancellation does not undo a write or remote side effect that already happened.
An automatic reaction uses ctx.withEvent(delivery) for resulting work, including after an await. Give causal subscriptions a stable ruleId where the contract requires it. User-initiated actions use the original activation context. This lets the host detect loops without conflating independent actions.
Build and install locally
Follow the checkout's package scripts. In the public plugin repository, the normal checks are:
bun run build
bun run typecheck
bun test
bun run validateOpen ReadAware → Settings → Plugins → Install plugin, select the built folder, and review the consent summary. Exercise the feature in the desktop app. Rebuild and reinstall to check an update.
Version private data
schemaVersion is independent of the package version. When the stored KV or document shape changes, supply the supported upgrade and downgrade transitions through migrate(storageCtx, change).
Migration receives storage-only authority. Test a failed transition as well as success: the prior package and committed data must remain usable. Avoid adding an unnecessary schema change for ordinary code updates.
Test the boundaries your feature uses
Check its actual Worker/Tauri behavior, permission and book-scope refusals, cancellation, disable/re-enable, and failure recovery. For a UI plugin, include keyboard navigation, long text, and narrow windows. For a data-changing plugin, include concurrent edits and restart where persistence matters.
Schedules run while the app is open; durable jobs support only the host's typed plans. Neither is a general background process or arbitrary-code job runner. The API reference describes these limits.
Publish
Once the built package works with its declared minimum contracts, follow Publishing.