Back to Blog

Send Desktop Notifications with Tauri | React + Rust Tutorial

Sandy LaneSandy Lane

Video: Send Desktop Notifications with Tauri | React + Rust Tutorial by Taught by Celeste AI - AI Coding Coach

Take the quiz on the full lesson page
Test what you've read · interactive walkthrough

tauri-plugin-notification plus permission requests, plus a simple Notification.title("...").body("...").show() pattern.

Native desktop notifications — the small banner that pops up at the corner of your screen when something interesting happens — are part of every modern app's vocabulary. Tauri exposes them via the notification plugin, which works on all three desktop platforms (macOS, Windows, Linux).

Install the plugin

cd src-tauri
cargo add tauri-plugin-notification
npm install @tauri-apps/plugin-notification

Register in lib.rs:

tauri::Builder::default()
  .plugin(tauri_plugin_notification::init())
  // ...

Add to capabilities/default.json:

"permissions": [
  "core:default",
  "notification:default"
]

Permissions: ask the user

Notifications require user consent on macOS and Windows. Check and request:

import {
  isPermissionGranted,
  requestPermission,
  sendNotification,
} from "@tauri-apps/plugin-notification";

async function notify(title: string, body: string) {
  let granted = await isPermissionGranted();
  if (!granted) {
    const permission = await requestPermission();
    granted = permission === "granted";
  }
  if (granted) {
    sendNotification({ title, body });
  }
}

The first call pops a system permission dialog. After that, the user's choice is remembered — subsequent calls don't ask again unless the user revokes the permission via system settings.

Sending from the frontend

import { sendNotification } from "@tauri-apps/plugin-notification";

sendNotification({
  title: "Build Complete",
  body: "Your project finished compiling.",
});

The simplest form. A title and a body. The OS shows a banner styled in its native idiom — not your CSS.

Sending from Rust

use tauri_plugin_notification::NotificationExt;

#[tauri::command]
fn notify_user(app: tauri::AppHandle, title: String, body: String) -> Result<(), String> {
  app.notification()
    .builder()
    .title(title)
    .body(body)
    .show()
    .map_err(|e| e.to_string())
}

Useful when the trigger is server-side or the message is generated on the Rust side.

Custom icon

sendNotification({
  title: "Hello",
  body: "This is a notification with an icon.",
  icon: "/path/to/icon.png",
});

Or, on the Rust side, the icon defaults to the app icon. For a custom icon, configure it via the plugin's builder.

Sound

sendNotification({
  title: "New message",
  body: "From Alice",
  sound: "default",
});

sound: "default" plays the OS's default notification sound. On macOS, you can also use named sounds: "Funk", "Glass", "Hero", etc.

When the user clicks a notification

Tauri 2 fires events when the user interacts with a notification:

import { onAction } from "@tauri-apps/plugin-notification";

onAction((notification) => {
  console.log("User clicked notification", notification);
});

Useful for "take me to the relevant screen" UX. Pair with deep linking — the notification carries an ID, the action handler navigates to the matching record.

Throttling

Don't fire ten notifications in five seconds. The OS often coalesces them, but more importantly, users find notification spam disrespectful and disable the app's permission. Send one summary instead of N individual notifications when something fires repeatedly.

Permission patterns

Always check before sending:

async function safeNotify(title: string, body: string) {
  if (!(await isPermissionGranted())) return;
  sendNotification({ title, body });
}

This way silent failures (user denied permission, system disabled them) don't propagate to your error logs.

Permission UX

The first time you call requestPermission() triggers a system dialog. That dialog appears once. If the user clicks "Don't Allow," subsequent requestPermission calls return the previous answer immediately — they don't ask again.

For a graceful first-time experience:

  • Don't ask on first launch. Wait until you have a reason.
  • Explain why you want notification permission in your own UI before triggering the system prompt.
  • Provide a settings toggle that respects the user's choice.

Practical example: build-complete notification

function BuildButton() {
  async function startBuild() {
    await invoke("compile_project");
    await notify("Build Complete", "Your project finished compiling.");
  }

  async function notify(title: string, body: string) {
    if (!(await isPermissionGranted())) {
      await requestPermission();
    }
    if (await isPermissionGranted()) {
      sendNotification({ title, body });
    }
  }

  return <button onClick={startBuild}>Build</button>;
}

The user clicks Build. The Rust command compiles (could take a while). When done, a notification appears. If the user is in a different window, they see the banner and click to come back.

Common mistakes

Asking for permission on app launch. Annoying. Wait until you actually need to send something.

Sending from a tight loop. OS-level rate limiting kicks in. Coalesce or throttle.

Not registering the plugin. Calling sendNotification without .plugin(tauri_plugin_notification::init()) in the builder fails silently.

Forgetting capabilities. ACL error if notification:default isn't granted.

Using long titles. The OS truncates. Keep titles under ~50 characters; bodies under ~200.

What's next

Next lesson: animated buttons and visual polish. A small detour into the front-end side — small motion details that make your app feel alive.

Recap

tauri-plugin-notification for desktop notifications. Check isPermissionGranted and requestPermission before sending. sendNotification({ title, body }) from JS or app.notification().builder().title(...).body(...).show() from Rust. Throttle to avoid spam. Capability notification:default is required.

Next: animated buttons.

Ready? Take the quiz on the full lesson page →
Test what you've learned. Watch the lesson and try the interactive quiz on the same page.