Sunflare Docs Beta

Build on Sunflare

Writing Plugins

Plugins are local, desktop-only scripts that add a small piece of UI to the app (a composer button, a right-click context menu item) without ever touching the network, your messages, or your account. No server, no publishing, no review process: install a .js file and it runs.

🔒

A plugin runs fully sandboxed in its own iframe with no network access at all, no access to the app's DOM, and no Node/Electron access. It can never read your messages, act on your behalf, or reach the network. There's no code path that lets it do any of that.

Install a plugin

Settings → Plugins (desktop app only) has an Install Plugin button that opens a native file picker filtered to .js files. Each installed plugin gets a toggle to enable/disable it and a remove action. Open Plugins Folder opens the actual folder on disk if you'd rather manage files by hand.

Anatomy of a plugin

A plugin is one JavaScript file starting with a manifest comment block:

// ==SunflarePlugin==
// @id          your.unique.id
// @name        My Plugin
// @version     1.0.0
// @author      Your Name
// @description What it does, in one line
// ==/SunflarePlugin==

// your code goes here

Only @id is required. Install fails without it. Everything else defaults (name → "Untitled Plugin", version → "0.0.0", author → "Unknown"). All fields are length-capped, so keep them short.

The sunflare API

Inside a plugin, a global sunflare object is the entire surface you have to work with:

CallDoes
sunflare.addComposerButton({ id, icon, title })Adds a button to the message composer's icon row. Max 3 per plugin.
sunflare.addContextMenuItem({ id, label })Adds an item to the message right-click menu. Max 3 per plugin.
sunflare.onClick(id, callback)Fires when the composer button or context-menu item with that id is clicked. The callback gets no arguments: no message content, no user data, just "it was clicked."
sunflare.notify({ title, body })Shows a real desktop notification. Title capped at 100 characters, body at 300. Limited to 10 per minute. Going over disables the plugin.
sunflare.playSound(dataUri)Plays a short sound. Must be a data:audio/mpeg, data:audio/wav, or data:audio/ogg base64 URI, ~300KB or smaller.

A complete example

This is public/plugins/example-plugin.js from the repo, verbatim. Install it as-is to see the whole thing work:

// ==SunflarePlugin==
// @id          sunflare.example.hello
// @name        Hello Plugin
// @version     1.0.0
// @author      Sunflare
// @description Adds one composer button that fires a desktop notification.
// ==/SunflarePlugin==

sunflare.addComposerButton({ id: 'hello-btn', icon: 'star', title: 'Say hi' });
sunflare.onClick('hello-btn', () => {
  sunflare.notify({ title: 'Hello Plugin', body: 'Composer button clicked.' });
});

What a plugin can't do

This is enforced structurally, not just by convention. There's simply no bridge for any of it:

Composer-button and context-menu clicks intentionally carry no payload. A plugin finds out a button was clicked, never what message or text it was clicked next to.

Limits worth knowing while building

Distributing a plugin

There's no store or publishing flow. Share the .js file however you like (a gist, a repo, a Sunflare server's file uploads). Anyone installing it goes through the same native file picker as any other install.