Skip to content

notifications ​

tiny.notify — banners, clicks, buttons and replies

tiny.notify() shows a desktop notification: a Notification Center banner on macOS, a toast on Windows, and whatever the desktop's notification service shows on Linux. Clicking it, or one of its buttons, comes back to your app as an event.

The backend has the same call as app.notify(title, body, opts), and hears clicks and buttons through the onNotificationClick and onNotificationAction hooks.

showing a notification ​

js
tiny.notify('Export finished', 'report.pdf is ready', { id: 'export-42', sound: true });

tiny.notify(title, body?, opts?):

option
idyour id for this notification, passed back on clicks and button presses. Set it whenever you want to hear back — on Linux nothing is reported without one
subtitlea second line under the title (on Linux it's joined to the body)
soundtrue plays the notification sound
actionsbuttons and reply fields — see buttons and replies

It never rejects, so there's no need to await it or catch errors. It resolves true once the notification has been handed to the OS — whether or not a banner appears, since the user may have turned notifications off for your app. The one exception is tinyjs dev on macOS, where it resolves false if the fallback banner couldn't be shown.

clicks ​

js
const stop = tiny.app.onNotificationClick((id) => {
  if (id === 'export-42') tiny.app.shell.reveal(exportPath);
});

tiny.app.onNotificationClick(fn) calls fn(id) when a notification is clicked, and returns a function that removes the handler. Clicks are reported for notifications sent with an id, except where noted under each OS.

buttons and replies ​

js
tiny.notify('New message', 'from Alex: lunch?', {
  id: 'msg-881',
  actions: [
    { id: 'reply', title: 'Reply', reply: true, placeholder: 'Message…' },
    { id: 'mute', title: 'Mute thread', destructive: true },
  ],
});

tiny.app.onNotificationAction(({ id, action, reply }) => {
  if (action === 'reply') sendReply(id, reply);   // reply: the typed text
  if (action === 'mute') muteThread(id);
});

Each action is { id, title } plus:

field
replyshow a text field instead of a plain button; what's typed arrives as reply
placeholderplaceholder text for the field
buttonTitlethe label of the field's send button, if different from title (macOS)
destructiveshow the button in red (macOS)

tiny.app.onNotificationAction(fn) calls fn({ id, action, reply }): the notification's id, the action's id, and the typed text ('' for a plain button). It returns a function that removes the handler. Clicking the notification itself, rather than a button, calls onNotificationClick instead.

Check support before relying on them: tiny.system.capabilities() reports notificationActions and notificationReply.

on each OS ​

shown asclicksbuttonsreply field
macOS, signed — any real signing identity, including Apple Developmenta Notification Center banner with your app's icon✓✓✓
macOS, tinyjs dev or ad-hoc signeda plain banner shown through AppleScript, credited to Script Editor———
Windowsa toast, or a tray balloon where toasts aren't available✓✓one per notification
Linuxthe desktop's notification servicewith an idwith an id— (shows as a plain button, reply is '')

Notification Center refuses ad-hoc-signed apps, which is why tinyjs dev and a build without a signIdentity fall back to AppleScript. Test clicks and buttons in a signed build.

permission (macOS) ​

The first notification from a signed app asks the user for permission. If they've turned notifications off for your app, notifications are dropped without an error. To find out first:

js
const status = await tiny.app.permissions.check('notifications');
// 'granted', 'denied', 'undetermined' — or 'unsupported' outside a packaged .app
if (status === 'undetermined') await tiny.app.permissions.request('notifications');

See permissions. Windows and Linux have no per-app permission for notifications, and answer 'granted'.

in the backend ​

js
export async function init(app) {
  app.notify('Sync complete', '12 files updated', { id: 'sync' });
}

export function onNotificationClick(id, app) {
  if (id === 'sync') app.win.show();
}

export function onNotificationAction({ id, action, reply }, app) {
  // same shape as the page's onNotificationAction
}

The page events are also on tiny.api.on as 'notification-click' and 'notification-action'. The older object form, app.notify({ title, body, … }), still works but logs a deprecation warning.

In an api gate the call is notify; the "wrapper" preset allows it.