Sending Notifications
Learn how to send native desktop and mobile notifications from your Tauri v2 app using the notification plugin, including basic messages, rich content, scheduling, and interactive actions.
Tauri does not include a built-in notification API. Instead, you use the community-maintained tauri-plugin-notifications plugin, which wraps the native notification system on each platform — Windows toast notifications, macOS Notification Center, Linux desktop notifications via D-Bus, and the native notification frameworks on Android and iOS. This document covers everything you need to send local notifications from your Tauri v2 application with a React + Vite frontend. Check Notification Permissions before sending, and see the Introduction for plugin setup.
Setting Up the Notification Plugin
Install the JavaScript package, add the Rust crate, register the plugin, and grant the required permissions.
Step 1: Install the Frontend Package
From your project root (where package.json lives), install the TypeScript/JavaScript bindings:
npm install @choochmeque/tauri-plugin-notifications-api
# or
pnpm add @choochmeque/tauri-plugin-notifications-api
Step 2: Add the Rust Plugin
In src-tauri/Cargo.toml, add the plugin as a dependency. Use the latest version (0.5.x at time of writing):
[dependencies]
tauri-plugin-notifications = "0.5"
If you need push notifications (FCM/APNs/UnifiedPush), enable the push-notifications feature:
tauri-plugin-notifications = { version = "0.5", features = ["push-notifications"] }
For simple local notifications, the default features are enough.
Step 3: Register the Plugin in the Rust Backend
Initialize the plugin inside your main function:
fn main() {
tauri::Builder::default()
.plugin(tauri_plugin_notifications::init())
.run(tauri::generate_context!())
.expect("error while running tauri application");
}
Step 4: Configure Permissions
The plugin needs explicit permission to send notifications. In your capability file (e.g., src-tauri/capabilities/default.json), add the notifications:default permission:
{
"identifier": "default",
"description": "Default capability",
"windows": ["main"],
"permissions": [
"core:default",
"notifications:default"
]
}
Permission Scopes:
The notifications:default permission covers sending and managing local notifications. If you only need to send notifications without managing channels or schedules, you can use a narrower permission set. Refer to the plugin’s permission documentation for details.
Checking Notification Permission
Before you can send a notification, the user must grant permission. The plugin provides isPermissionGranted() and requestPermission() functions.
Since the dedicated permissions chapter goes deeper, here is the minimal flow you need before calling sendNotification():
import {
isPermissionGranted,
requestPermission,
sendNotification,
} from '@choochmeque/tauri-plugin-notifications-api';
async function ensurePermission(): Promise<boolean> {
if (await isPermissionGranted()) {
return true;
}
const permission = await requestPermission();
return permission === 'granted';
}
Permission must be requested after a user gesture:
On many platforms, calling requestPermission() without a prior click or tap will fail. Always trigger permission requests from a button or similar user interaction.
Sending a Basic Notification
Once permission is granted, you can send a notification with a single string (the title) or with an options object.
import { useState } from 'react';
import {
isPermissionGranted,
requestPermission,
sendNotification,
} from '@choochmeque/tauri-plugin-notifications-api';
function App() {
const [permissionChecked, setPermissionChecked] = useState(false);
async function handleSendSimple() {
// Ensure permission (guard)
if (!(await isPermissionGranted())) {
const perm = await requestPermission();
if (perm !== 'granted') return;
}
setPermissionChecked(true);
await sendNotification('Hello from Tauri!');
}
async function handleSendWithBody() {
if (!(await isPermissionGranted())) return;
await sendNotification({
title: 'TAURI',
body: 'Tauri is awesome!',
});
}
return (
<div>
<button onClick={handleSendSimple}>Send Simple Notification</button>
<button onClick={handleSendWithBody}>Send Title + Body</button>
</div>
);
}
export default App;
A notification with only a title will show the title in the OS notification center. Adding a body provides the secondary text. Not all platforms render the body identically, but the plugin normalizes as much as possible.
Notification Content
Title and Body
- title (
string, required) — The main heading of the notification. On most systems this appears in bold. - body (
string, optional) — Additional explanatory text displayed below the title. Keep it concise; a long body may be truncated by the OS.
These two fields are the core of every notification. You can send them directly without any other options and the plugin will use sensible defaults for icon, sound, and auto-cancel behavior.
Icons
The icon option accepts a string that identifies a bundled resource or a system icon name. The exact meaning varies by platform:
- macOS: The string is treated as an image name from the app bundle’s assets.
- Windows: You can pass a path to a
.icofile or use a system icon identifier. - Android: The value maps to a drawable resource name.
- Linux: The string is forwarded to the notification daemon; most support standard icon names (e.g.,
"dialog-information").
await sendNotification({
title: 'Download Complete',
body: 'project.zip has been saved.',
icon: 'download_complete', // platform‑specific resource name
});
Icons are not automatic:
The plugin does not fall back to a default app icon. If you provide a name that doesn’t exist on the target platform, the notification will appear without an icon. Bundle your icon assets appropriately for each platform.
Notification Options
The plugin’s sendNotification() accepts a rich options object. The properties below let you control identity, behavior, scheduling, layout, and interactivity.
Notification ID and Replacement
Use id to assign a unique number to a notification. When you send a notification with the same id as one that is still active or pending, the new notification replaces the old one. This prevents flooding the user with duplicates.
// First call
await sendNotification({ id: 42, title: 'Update', body: 'Version 1.1 available' });
// Later, with same id — replaces previous notification
await sendNotification({ id: 42, title: 'Update', body: 'Version 1.2 now available' });
If you omit id, the plugin generates a random one internally. Replacing notifications only works when you explicitly manage IDs.
Collision risk without explicit IDs:
Without an id, each sendNotification call creates a separate notification. If a loop accidentally sends many notifications, the user’s screen fills up. Always provide an id for updates that should replace earlier ones.
Auto-Cancel Behavior
By default, autoCancel is true: tapping or clicking the notification dismisses it automatically. Setting autoCancel to false keeps the notification in the tray until the user explicitly clears it or your code cancels it programmatically.
await sendNotification({
title: 'Persistent Alert',
body: 'This stays until you dismiss it.',
autoCancel: false,
});
Importance and Channels (Android)
On Android, notifications are organised into channels. A channel groups related notifications and lets users control their behaviour (sound, vibration, importance) from system settings. The channelId and importance options only affect Android; they are ignored on other platforms.
import { Importance } from '@choochmeque/tauri-plugin-notifications-api';
await sendNotification({
title: 'Direct Message',
body: 'You have a new message',
channelId: 'chat_messages',
importance: Importance.High,
});
channelId— string identifier for the channel. If the channel doesn’t exist, the plugin creates it with the given importance.importance— enum fromImportance:None,Min,Low,Default,High,Max.Highmakes a heads‑up notification that pops up even when the user is in another app.
Channel creation is one-shot:
Once a channel is created with a given importance, the user can override it in Settings, but your app cannot change the importance of an existing channel programmatically. Choose importance levels carefully.
Scheduling Notifications
You can delay a notification to a specific point in time or set it to repeat.
import { Schedule } from '@choochmeque/tauri-plugin-notifications-api';
// Show on January 15, 2026 at 14:30
await sendNotification({
title: 'Meeting Reminder',
body: 'Team stand‑up in 5 minutes',
schedule: Schedule.at(new Date(2026, 0, 15, 14, 30)),
});
// Repeat every day at 9:00 AM
await sendNotification({
title: 'Daily Tip',
body: 'Drink water!',
schedule: Schedule.at(new Date(2026, 0, 1, 9, 0), true),
});
The scheduler uses the local device time. On Android, scheduled notifications survive app restarts; on desktop platforms, the plugin uses OS‑level task scheduling where available.
Inbox-Style and Large Text Notifications
For content that doesn’t fit in a short body, the plugin supports two special layouts.
- Inbox style: displays multiple lines as a list. Useful for email summaries, message previews, or log entries.
- Large text: shows a longer piece of text when the user expands the notification.
// Inbox style
await sendNotification({
id: 10,
title: 'New Emails',
body: '3 unread messages',
inboxLines: [
'Alice: Project update',
'Bob: Lunch tomorrow?',
'Charlie: Invoice attached',
],
summary: 'Inbox',
});
// Large text
await sendNotification({
id: 11,
title: 'Article Available',
body: 'Tap to read the full article',
largeBody: 'This is the full article text that appears when the user expands the notification...',
summary: 'Read more',
});
These options are handled natively on Android and Windows. On macOS and Linux, the plugin may fall back to a simple body with all text concatenated.
Interactive Actions
You can attach one or more action buttons to a notification. When the user taps an action, your app receives an event with the action’s ID.
await sendNotification({
id: 20,
title: 'File Downloaded',
body: 'report.pdf is ready.',
actions: [
{ id: 'open', title: 'Open' },
{ id: 'delete', title: 'Delete' },
],
});
To respond to action presses, listen for the actionPerformed event:
import { addNotificationActionListener } from '@choochmeque/tauri-plugin-notifications-api';
addNotificationActionListener((action) => {
if (action.actionId === 'open') {
console.log('User wants to open the file');
}
});
Action handling requires the app to be running:
On most platforms, tapping an action brings your app to the foreground. If your app is completely closed, the action event may not be delivered until the app launches. For background action handling, consider push notification service extensions.
Grouping Notifications
Assign a group string to multiple notifications to have the OS visually group them together.
await sendNotification({ id: 1, title: 'Chat', body: 'Alice: Hi', group: 'team_chat' });
await sendNotification({ id: 2, title: 'Chat', body: 'Bob: Good morning', group: 'team_chat' });
You can also set a groupSummary flag (Android‑specific) to indicate that a notification is the group summary.
Custom Sounds
The sound option lets you play a custom audio file when the notification arrives. Provide the resource name (platform‑dependent, similar to icons).
await sendNotification({
title: 'Alarm',
body: 'Wake up!',
sound: 'alarm_sound',
});
On desktop, the sound file must be accessible to the plugin. On Android, it maps to a raw resource.
Sound files must be bundled:
If the sound resource is missing, the notification will use the default system sound (or no sound). Always test on each platform.
Handling Errors and Events
The sendNotification function returns a promise. If the underlying platform fails (e.g., missing permission, invalid channel, or the notification service is unavailable), the promise rejects.
Wrap your calls in a try/catch to handle these gracefully:
try {
await sendNotification({ title: 'Reminder', body: 'Time is up' });
} catch (error) {
console.error('Failed to send notification:', error);
// Fallback: show an in‑app alert
}
Common reasons for failure include:
- Permission not granted (
permission deniederror) - Calling
sendNotificationbefore any user gesture when the permission prompt hasn’t been shown - Using an invalid
channelIdon Android without the app having created it first (the plugin creates it for you, but only once)
Summary
You’ve learned how to send local notifications from a Tauri v2 app using the tauri-plugin-notifications plugin. The fundamental call is sendNotification({ title, body }), and from there you can layer on IDs for replacement, scheduling, rich layouts, action buttons, and platform‑specific options like Android channels.