Skip to main content

Manifest

The timon field in package.json declares the plugin contract. The npm package uses type: "module"; main points to host code, when present, and exports["./client"] points to the browser entry.

FieldContract
idRequired; 3–40 lowercase letters, digits or hyphens in the current third-party validator; hooks is reserved
apiVersionRequired positive integer; minimum supported API contract
kindOne kind or a nonempty list; inferred from slots when absent
label, description, authorOptional nonempty display strings
coverOptional package-relative image path, not a remote URL
iconOptional package-relative SVG: the picto of the gallery card, painted as a mask in the theme's text colour (only its shape counts; a 24×24 line drawing reads best). Not a remote URL
screenshotsAdditional package-relative images; default []
permissionsRequested capabilities; default []
client.slotsAdmitted UI surfaces; default []
client.externalExternal module declarations; default []
client.modeesm by default; schema also accepts iframe
declaresSlots opened for other components, mapped to single, list or keyed
routePage route; required for a page, forbidden otherwise. Agents find it with their ui_routes tool, titled with your label, for the users who can open your plugin
modifiesInformational list of affected surfaces; default []
containsChild manifest IDs for bundles only
settingsDeclarative fields: key, type, label, optional default
webhooksIncoming webhooks: name (lowercase slug) and auth, the sender's signature format — standard (default), github, gitea, forgejo, gitlab, stripe, slack, shopify, linear, discord or meta. Any value other than standard needs apiVersion: 8. Needs webhooks:receive (Receiving webhooks)
license.requiredWhether the licence must explicitly permit the plugin; default false

Slot names have lowercase alphanumeric dot-separated segments beginning with a letter. Routes start with /, use lowercase alphanumeric or hyphenated segments and contain no query or traversal. Covers and screenshots are confined to the package and accept PNG, WebP, JPG, JPEG or SVG paths.

Unknown manifest keys are ignored. Known fields with invalid values, incompatible kinds/slots or an API version newer than the daemon are refused. Use the real templates' ESM mode for browser plugins; an accepted schema option alone does not demonstrate an alternate runtime integration.

Shared browser modules​

Keep these imports external to a browser bundle:

react
react-dom
@timon/ui
@timon/api
@timon/theme
@timon/navigate

For esbuild, use format: 'esm', bundle: true and an external array containing those exact specifiers. Do not embed another React instance. Include the built browser file in the published package: installation does not run lifecycle scripts.

Host entry​

Export apply(ctx) and register routes or effects through the context. ctx.router() mounts routes beneath /api/plugins/<encoded-id>/ and removes them when the plugin unloads. The /-/ path prefix is reserved for platform endpoints. The router is an Express router. A handler may be async: if it throws or its promise rejects, Timon logs the error under your plugin's id and answers 500 {"error":"internal_error"} without the error's message, and the daemon keeps running. An error carrying a 4xx status keeps that status. Catch the errors you can explain and answer them yourself: the generic 500 tells your user nothing.

Outside a route, the same holds for a promise nobody awaits (void doWork()), an async timer callback or an event listener that throws: the daemon logs the error under your plugin's id, keeps running, and posts one notification naming your plugin (at most once an hour, without the error's text). A listener of ctx.events.on that throws does not stop the other listeners of the event. Repeats of the same error within a minute are counted rather than logged one by one. This is a safety net, not error handling: the work that failed did not happen, so catch what you can recover from.

If you export inject, it may name only kanban, missions, notify, storage, settings, events, http and host. It may not name db, bus or another internal service. Requested permissions are separate from dependency injection.