Skip to content

Authoring

Roy Padina edited this page Oct 23, 2026 · 2 revisions

Authoring

A plugin adds conditions (true/false checks) and actions (things to do) to NtfyBar's rules. It is a folder with a plugin.json manifest and one JavaScript file. It runs in a sandbox. Canonical text: docs/authoring.md.

Folder layout

com.example.my-plugin/      folder name = plugin id
├── plugin.json
└── main.js

Files sit directly in the folder (no subfolders). Every file must be listed in files.

plugin.json

{
  "id": "com.example.my-plugin",
  "name": "My Plugin",
  "version": "1.0.0",
  "description": "One line, up to 120 characters.",
  "author": { "name": "Your Name", "url": "https://github.com/you" },
  "apiVersion": 1,
  "minAppVersion": "1.0.0",
  "entry": "main.js",
  "files": ["plugin.json", "main.js"],
  "permissions": ["ui"],
  "permissionReasons": { "ui": "Shows the build result in a popup." },
  "providers": [
    {
      "id": "com.example.my-plugin.failed-build",
      "kind": "condition",
      "name": "Build failed",
      "description": "The message reports a failed build.",
      "function": "isFailedBuild",
      "params": [{ "key": "branch", "label": "Branch", "kind": "text", "placeholder": "main" }]
    }
  ],
  "popup": { "engine": "declarative" }
}
Field Rules
id lowercase reverse-DNS, up to 100 characters, equals the folder name
name up to 60 characters
description required, up to 120 characters
apiVersion 1
minAppVersion optional, oldest NtfyBar that can run it
entry the JavaScript file, listed in files
files every file, plugin.json included; letters, digits, _ . -, no leading dot
permissions see Permissions
permissionReasons required for each permission, up to 120 characters, shown to the user
providers at least one
popup optional, {"engine": "declarative"} is the only engine today

Each provider:

Field Rules
id starts with your plugin id plus a dot
kind condition or action
name, description shown in the rule editor, description up to 120 characters
function a global function in the entry file
params optional {key, label, kind, placeholder}; kind is text, number, bool, topic, or (1.1+) choice with "choices": ["a", "b"] (2–20 values)

A package with an invalid manifest is not loaded. A broken provider is skipped on its own. The Plugins tab lists every problem.

JavaScript

// condition: return true or false
function isFailedBuild(ctx, params) {
  return /failed/i.test(ctx.message.title || "") &&
    (!params.branch || ctx.message.message.indexOf(params.branch) >= 0);
}

// action: return undefined, or {popup: …} (needs "ui")
function showResult(ctx, params) {
  return { popup: { title: ctx.message.title, accent: "critical", blocks: [{ text: ctx.message.message }] } };
}

// optional: called when a button on your popup is pressed
function onPopupAction(ctx, buttonId, state) {
  return { close: true };
}

Any other return type, or a thrown exception, is logged as a provider error. NtfyBar keeps running.

ctx

{
  message: {
    id, time,            // ntfy message id, unix seconds
    title, message,
    priority,            // 1 (min) … 5 (urgent)
    tags,                // array of strings
    click, icon,
    actions, attachment, // as ntfy sends them
    contentType,         // "text/markdown" for markdown messages
    isCatchUp            // true if it arrived while NtfyBar was offline
  },
  topic: { id, label }
}

Topic names and server addresses never reach a plugin. Where one appears in a message, it becomes [topic] or [server].

params

The values the user entered in the rule editor, by key. topic params hold a topic id.

ntfy

Each ntfy method exists only while its permission is granted, and the user can switch permissions off at any time. Check first:

if (ntfy.publish) ntfy.publish({ topicId: ctx.topic.id, message: "seen" });

Environment

  • Plain JavaScript (JavaScriptCore). No fetch, XMLHttpRequest, require, timers, process or DOM.
  • A call may run for 1 second. After three timeouts the plugin is disabled until the user reloads plugins.
  • Calls run on NtfyBar's main thread: keep them short.
  • Call ntfy methods inside your functions, not at the top level.

Try it locally

  1. NtfyBar › Settings › Plugins › Sources › Add Folder…, pick your plugin folder.
  2. Installed › your plugin › Review and Enable….
  3. Settings › Rules › add a rule that uses your condition or action.
  4. After editing, press Reload Plugins.

Local plugins are marked Unverified source. Logs:

/usr/bin/log show --last 10m --predicate 'subsystem == "com.padina.ntfybar" AND category == "plugins"'

Limits

What Limit
One file 2 MB
Whole package 10 MB
One call 1 s (3 timeouts disable until reload)
ntfy.history 200 messages per call
ntfy.publish 10 per minute per plugin; quiet hours cap the priority
ntfy.storage 1 MB per plugin
ntfy.copy 1 MB of text
Popup 20 blocks, 3 buttons, text block 4 KB, 50 table rows, state 64 KB

Next: Permissions, Popup Spec, Publishing.

Clone this wiki locally