Notification API
Learn how to send native OS notifications from your Tauri v2 app using the notification plugin — setup, permissions, sending, and advanced features like channels and actions.
The notification plugin gives your Tauri app access to the operating system’s built‑in notification system. A notification is a brief message that appears outside your app window — in the system tray, as a banner, or on the lock screen — even when the app is in the background. This is how chat apps tell you about a new message, or a to‑do app reminds you of an overdue task. The plugin provides a single JavaScript API that works across Windows, macOS, Linux, iOS, and Android, with the same familiar pattern of checking permission first, then sending the notification. The Introduction covers setup in isolation.
Windows development behaviour:
On Windows, native notifications are only fully supported once the app is installed as a package. During development (tauri dev) you will see a PowerShell‑style popup with your app’s process name instead of a rich toast. Linux, macOS, iOS, and Android show real notifications in development.
Installation
The plugin has a Rust side (the backend) and a JavaScript side (the frontend). The easiest way to add both is with Tauri’s automatic installer, but you can also set everything up manually.
Automatic setup
Run one command from your project root. It adds the crate, registers the plugin, and installs the npm package for you.
npm run tauri add notification
The automatic installer handles all the steps below. Skip to Notification Permissions unless you prefer to know what is happening under the hood.
Manual setup
If you need to add the plugin piece by piece, follow these steps in order.
Step 1: Add the Rust crate
From the src-tauri directory, add the plugin to Cargo.toml:
cargo add tauri-plugin-notification
Step 2: Register the plugin in lib.rs
Open src-tauri/src/lib.rs and call .plugin(tauri_plugin_notification::init()) on the builder:
// src-tauri/src/lib.rs
#[cfg_attr(mobile, tauri::mobile_entry_point)]
pub fn run() {
tauri::Builder::default()
.plugin(tauri_plugin_notification::init())
.run(tauri::generate_context!())
.expect("error while running tauri application");
}
Step 3: Install the JavaScript package
The npm package lets you call the plugin from your React frontend.
npm install @tauri-apps/plugin-notification
Step 4: Grant permissions in the capability file
The plugin needs explicit permissions. In src-tauri/capabilities/default.json add "notifications:default" to the permissions array:
// src-tauri/capabilities/default.json
{
"identifier": "default",
"description": "Default capability for the main window",
"windows": ["main"],
"permissions": [
"core:default",
"notifications:default"
]
}
Missing permission will cause silent failure:
If you skip this step, calls from JavaScript will throw a permission error or do nothing. Always check that the capability file includes the notification permission.
Verify installation:
After completing the setup, restart the dev server. If the app launches without errors, the plugin is loaded correctly. You can confirm by calling isPermissionGranted() — even if permission is not yet granted, the function should return false rather than throwing an import error.
Notification Permissions
Every platform requires the user to grant permission before your app can show notifications. The Notification Permissions page covers platform quirks in more detail. The plugin exposes two functions for this: isPermissionGranted() checks the current status, and requestPermission() shows the system dialog to ask the user.
The permission state can be one of three values:
granted— the user allowed notifications.denied— the user explicitly refused. You cannot ask again from code; the user must change it in system settings.prompt— the user has not yet made a decision (or was never asked). This is the initial state on most platforms.
The standard workflow is: check permission first, request if needed, and only send the notification when the state is granted.
Here is a minimal React component that handles the permission flow before sending a notification.
// src/App.tsx
import { useState } from "react";
import {
isPermissionGranted,
requestPermission,
sendNotification,
} from "@tauri-apps/plugin-notification";
function App() {
const [status, setStatus] = useState<string>("unknown");
async function notify() {
let permitted = await isPermissionGranted();
if (!permitted) {
const result = await requestPermission();
permitted = result === "granted";
}
if (permitted) {
sendNotification({ title: "Hello!", body: "This is a native notification." });
setStatus("sent");
} else {
setStatus("denied");
}
}
return (
<div>
<button onClick={notify}>Send notification</button>
{status === "sent" && <p>Notification sent.</p>}
{status === "denied" && (
<p style={{ color: "red" }}>
Permission denied. Enable notifications in your system settings.
</p>
)}
</div>
);
}
export default App;
When you click the button for the first time, the OS will show a permission dialog. The user’s choice is remembered across app launches.
Do not spam the permission dialog:
requestPermission() triggers a system‑level prompt. Calling it on every app start, or without a user gesture (like a click), will annoy users and may cause the OS to auto‑deny. Always first check isPermissionGranted(), and only request permission after an explicit user action, such as clicking an “Enable notifications” button.
Sending Notifications
Once permission is granted, sending a notification is a single function call. You pass an object with at least a title and optionally a body, an icon, a sound, or a channel identifier.
import { sendNotification } from "@tauri-apps/plugin-notification";
// Simplest form — the title is required.
sendNotification({ title: "Download complete" });
// With a body and a custom identifier.
sendNotification({
id: 1,
title: "New Message",
body: "You have a new message from Alex",
});
The notification uses the platform’s native appearance: a banner on macOS, a toast on Windows, a bubble on Linux, and a card on mobile. The id field lets you update or cancel a notification later using cancel or batch commands, though for simple cases you can omit it.
If the app is in the foreground when the notification arrives, the OS may still show it depending on the platform. This is normal — notifications are intended to communicate events regardless of whether the app is active.
Notification Channels (Android)
On Android, notifications are organised into channels. A channel defines the behaviour of all notifications sent to it: importance level, sound, vibration, LED colour, and privacy settings. The user can manage channel settings from the Android system preferences, and if your app targets Android 8.0 (API level 26) or higher, every notification must belong to a channel.
The API still works on other platforms — createChannel is a no‑op on iOS, macOS, Windows, and Linux — so you can safely call it everywhere.
Create a channel once, typically on app startup, before sending any notifications that reference it.
import { createChannel, Importance, Visibility } from "@tauri-apps/plugin-notification";
async function setupChannel() {
await createChannel({
id: "messages",
name: "Messages",
description: "Notifications for new chat messages",
importance: Importance.High,
visibility: Visibility.Private,
vibration: true,
sound: "notification_sound",
lights: true,
lightColor: "#ff0000",
});
}
The importance field controls how the notification interrupts the user. Importance.High makes a sound and pops up on screen; Importance.Low appears silently in the tray. The visibility field determines how much content is shown on the lock screen.
After creating the channel, send notifications to it by adding a channelId:
sendNotification({
title: "New Message",
body: "Alex: Are you free later?",
channelId: "messages",
});
Invalid channel ID:
If the channel ID does not match an existing channel, the notification will not appear on Android. Always create the channel before sending notifications that reference it.
You can list existing channels with channels() and delete one with removeChannel("messages").
Notification Actions
Actions add interactive buttons to a notification. The user can tap a button to trigger a function in your app, even if the app is currently in the background.
An action requires two pieces: a registration step that tells the OS what buttons exist, and a listener that fires when the user taps one.
import { registerActionTypes, onAction } from "@tauri-apps/plugin-notification";
import { useEffect } from "react";
function useNotificationActions() {
useEffect(() => {
// Register the action type once
registerActionTypes([
{
id: "message-actions",
actions: [
{
id: "reply",
title: "Reply",
input: true,
inputButtonTitle: "Send",
inputPlaceholder: "Type your reply...",
},
{
id: "mark-read",
title: "Mark as Read",
foreground: false,
},
],
},
]);
// Listen for user interaction
const unlisten = onAction((event) => {
console.log("Action performed:", event.actionId, event.input);
});
return () => {
unlisten.then((fn) => fn());
};
}, []);
}
The input: true action turns the button into a text field — the user can type a reply directly from the notification. The typed value is available as event.input in the listener. The foreground: false option means tapping “Mark as Read” will not bring the app window to the front.
Attachments
You can attach an image or a media file to a notification so the user sees a preview alongside the text. The file must be a local asset inside your Tauri bundle, referenced with an asset:// or file:// protocol.
sendNotification({
title: "New Screenshot",
body: "Screenshot saved to gallery",
attachments: [
{
id: "preview",
url: "asset:///screenshots/demo.png",
},
],
});
Platform support varies: macOS, iOS, and Android handle image attachments well. Windows and Linux may not display them in all notification styles. Test attachments on every platform you target.
Practical Example — A Full Notification Hook
Putting everything together, here is a custom React hook that wraps permission checking, channel setup, and sending a notification. Further practical examples — task alerts, download completion, scheduled reminders — live on the dedicated page. It abstracts the repeated boilerplate and ensures the channel is created only once.
// src/hooks/useNotification.ts
import { useState, useCallback } from "react";
import {
isPermissionGranted,
requestPermission,
sendNotification,
createChannel,
Importance,
Visibility,
} from "@tauri-apps/plugin-notification";
let channelCreated = false;
export function useNotification() {
const [permission, setPermission] = useState<"granted" | "denied" | "unknown">("unknown");
const ensurePermission = useCallback(async (): Promise<boolean> => {
const granted = await isPermissionGranted();
if (granted) {
setPermission("granted");
return true;
}
const result = await requestPermission();
const permitted = result === "granted";
setPermission(permitted ? "granted" : "denied");
return permitted;
}, []);
const notify = useCallback(
async (title: string, body?: string) => {
const permitted = await ensurePermission();
if (!permitted) return;
if (!channelCreated) {
await createChannel({
id: "general",
name: "General",
description: "General notifications",
importance: Importance.Default,
visibility: Visibility.Public,
});
channelCreated = true;
}
sendNotification({
title,
body,
channelId: "general",
});
},
[ensurePermission]
);
return { notify, permission };
}
Use it in any component:
// src/App.tsx
import { useNotification } from "./hooks/useNotification";
function App() {
const { notify, permission } = useNotification();
return (
<div>
<button onClick={() => notify("Reminder", "Time to stand up!")}>
Send reminder
</button>
{permission === "denied" && (
<p style={{ color: "red" }}>
Notifications are blocked. Please enable them in your device settings.
</p>
)}
</div>
);
}
export default App;
Everything working?:
If you click the button and a system notification appears with your title and message, the full pipeline — permission, channel, and sending — is correctly set up.
Security Considerations
The notification plugin does not introduce new attack surfaces beyond what a user expects from an app that can send notifications. There are no known security vulnerabilities. The usual input sanitisation rules apply if you pass user‑generated content into the title or body — malicious text will appear in the notification tray the same way it would appear in any app that displays user input.
Permissions are enforced through the capability file you configured during setup. If a window lacks the notifications:default permission, all JavaScript calls will fail. This guarantees that even if a part of your frontend is compromised, it cannot silently send notifications without your explicit intent.
Summary
The notification plugin gives your app a direct line to the user’s attention outside the app window, with a consistent API across all Tauri targets. The key workflow — check permission, request if needed, send — stays the same whether you show a simple alert, an interactive message with reply buttons, or a notification organised into an Android channel.
Introduction to the Notification API
Understand what the Tauri Notification API is, how to install the required plugin, configure permissions, and send your first system notification.
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.
Notification Permissions
Understand how to check, request, and manage notification permissions in Tauri v2 across macOS, Windows, Linux, Android, and iOS, with complete React examples and platform-specific behavior.
Practical Examples
Real-world notification patterns for Tauri apps using React, covering task alerts, download completion, scheduled reminders, and interactive reply actions.