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.

Before your Tauri app can display a system notification, the user must grant permission. Each operating system has its own permission model. Tauri v2’s notification plugin exposes a uniform API to check and request that permission, but the underlying platform behavior still matters. This guide covers every permission state, how to handle denial gracefully, and platform quirks you need to know.

Why Notification Permissions Exist

Notifications can interrupt whatever the user is doing. An unexpected pop-up, sound, or badge can feel intrusive. Forcing apps to get explicit consent gives the user control over which applications can grab their attention. It also prevents malicious sites or apps from spamming the desktop with fake system alerts. In a Tauri app, the operating system manages permissions. Tauri’s plugin acts as a bridge — it asks the OS to show the system permission dialog and then reports the result back to your JavaScript code. If the user says “no,” your app cannot override that decision.

How Tauri v2 Exposes Permissions

The notification plugin gives you two JavaScript functions:

  • isPermissionGranted() — returns true if the user has already granted notification permission.
  • requestPermission() — shows the system dialog (if the OS allows it) and resolves to either 'granted' or 'denied'. These functions are available from the package @tauri-apps/plugin-notification. The plugin itself must be initialized on the Rust side, and the required permissions must be present in the app’s capability configuration. If you set up the plugin via tauri add notification, the default permission set includes all the necessary entries, so you can start using these functions immediately.

Default permissions cover permission checks:

The plugin’s default permission set includes allow-is-permission-granted, allow-request-permission, and allow-notify. Unless you’ve locked down capabilities manually, your app can already check and request notification permissions.

Checking the Current Permission Status

Before you ask for permission, always check whether it has already been granted. Calling requestPermission unnecessarily can annoy users who have already made a choice. The isPermissionGranted function returns a simple boolean.

import { isPermissionGranted } from '@tauri-apps/plugin-notification';

async function checkPermission() {
  const granted = await isPermissionGranted();
  if (granted) {
    console.log('Permission already granted — notifications can be sent.');
  } else {
    console.log('Permission has not been granted yet.');
  }
}

This is a fast, non-intrusive call. It does not trigger any dialog. On most platforms it reflects what the OS reported the last time the app launched. There is, however, an important platform caveat you must be aware of.

macOS may report outdated permission state:

On macOS, isPermissionGranted can return true even after the user manually disables notifications for your app in System Settings. The plugin reads the initial grant status and does not refresh it when the system toggle changes. This is a known limitation. If you need to detect a real-time change, the most reliable workaround is to attempt to send a notification and observe whether it appears — though even that is not perfectly deterministic. The issue is tracked in the plugin repository and may be resolved in a future update.

Requesting Permission from the User

When isPermissionGranted returns false (or you are in a context where you must prompt the user for the first time), call requestPermission. The function returns a promise that resolves to either 'granted' or 'denied'.

import { requestPermission } from '@tauri-apps/plugin-notification';

async function askForPermission() {
  const result = await requestPermission();
  if (result === 'granted') {
    console.log('User allowed notifications.');
  } else {
    console.log('User denied notifications.');
  }
}

Request permission only in response to a user gesture:

On several platforms (notably Android and iOS), the system will not show a permission dialog unless the request is triggered directly by a user action — such as a button click. If you call requestPermission automatically on app startup, the dialog may be suppressed and the promise will resolve to 'denied' without the user ever seeing the prompt. Always bind the call to a click handler or similar explicit interaction.

On some desktop platforms, particularly macOS, calling requestPermission when the user has already denied notifications through System Settings will not show a dialog again. It will simply resolve to 'denied'. The only way for the user to change the decision is to open the OS notification settings manually.

Platform-Specific Behavior

Notification permission behavior varies significantly across operating systems. The table below summarizes the key differences, and the following tabs explain each platform in more detail.

PlatformPermission PromptGrant After DenialNotable Quirk
macOSSystem dialog on first request, then ignored if manually toggled offMust re-enable in System Settings > NotificationsisPermissionGranted may not reflect manual toggles
WindowsUsually no prompt if app is installed; dev mode shows PowerShell identityCan be toggled in Settings > System > NotificationsPermission almost always granted for installed apps
LinuxDepends on desktop environment; many show no promptDesktop-specific (usually via system settings)No unified behavior; test per distribution
AndroidRuntime dialog on first request (Android 13+)User can re-enable in App Info > NotificationsRequires POST_NOTIFICATIONS permission declaration (handled by plugin)
iOSSystem dialog on first request; provisional authorization available via native codeMust re-enable in Settings > NotificationsTauri plugin does not expose provisional authorization directly

The first time your app calls requestPermission, macOS shows its standard notification permission dialog. If the user clicks “Allow,” the status becomes granted. If they click “Don’t Allow,” it becomes denied. Subsequent calls to requestPermission will not show the dialog again — the user must go to System Settings \u003e Notifications, find your app, and manually change the toggle. The isPermissionGranted function returns the cached status from when the app was launched, so if the user changes the toggle while the app is running, your code won’t see the new value until the app restarts (and even then, the bug mentioned earlier may persist).

A Complete Permission Flow

The following steps walk through the recommended pattern: check permission, request if needed, then send a notification. Each step builds on the previous one. The full React component is shown at the end.

1

Step 1: Check existing permission

Start by calling isPermissionGranted. If it returns true, you can skip the request and go directly to sending a notification. This avoids showing a redundant dialog.

const alreadyGranted = await isPermissionGranted();
if (alreadyGranted) {
  // proceed to send notification
}
2

Step 2: Request permission (user-triggered)

If permission is not yet granted, bind requestPermission to a button click. Do not call it on page load. Wait for the user to interact with a clear call-to-action, such as a “Enable Notifications” button.

<button onClick={handleRequestPermission}>
  Enable Notifications
</button>

Inside the handler, call requestPermission and store the result.

const handleRequestPermission = async () => {
  const result = await requestPermission();
  setPermission(result); // store in state
};
3

Step 3: Send the notification after grant

Once the result is 'granted', you can safely send a notification. Always check the stored permission state before calling sendNotification to avoid a failed attempt.

if (permission === 'granted') {
  sendNotification({ title: 'Hello', body: 'Notifications are enabled!' });
}

Here is a complete, runnable React component that implements this flow. It uses a single button to check, request, and then send a test notification.

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

function NotificationPermissionButton() {
  const [permission, setPermission] = useState<string | null>(null);
  const [checking, setChecking] = useState(false);

  const handleEnableNotifications = async () => {
    setChecking(true);

    // Step 1: check current status
    const alreadyGranted = await isPermissionGranted();
    if (alreadyGranted) {
      setPermission('granted');
      sendNotification({ title: 'Already enabled', body: 'Notifications were already on.' });
      setChecking(false);
      return;
    }

    // Step 2: request permission
    const result = await requestPermission();
    setPermission(result);

    // Step 3: send a test notification if granted
    if (result === 'granted') {
      sendNotification({ title: 'Notifications On', body: 'You will now receive alerts.' });
    }

    setChecking(false);
  };

  return (
    <div>
      {permission === 'granted' ? (
        <p>✅ Notifications are enabled.</p>
      ) : permission === 'denied' ? (
        <p>❌ Notifications are denied. Please enable them in your system settings.</p>
      ) : (
        <button onClick={handleEnableNotifications} disabled={checking}>
          {checking ? 'Checking...' : 'Enable Notifications'}
        </button>
      )}
    </div>
  );
}

export default NotificationPermissionButton;

When the user clicks the button for the first time, the app checks whether permission already exists. If not, it calls requestPermission. On macOS, iOS, or Android, the system dialog appears at that moment. If the user grants permission, a test notification is sent immediately so they can confirm it works. If they deny, the UI updates to show a message directing them to system settings.

If your test notification appears, everything is configured correctly:

Seeing the test notification confirms that the plugin is initialized, capabilities are set, and the OS has granted permission. If the notification does not appear, check the browser console for errors and verify that the notification permission in the OS settings is enabled.

Handling Denial and Guiding the User

When requestPermission resolves to 'denied', your app cannot send notifications through the normal API. It’s important not to simply fail silently. Show a clear message that explains the situation and, where possible, tells the user how to manually re-enable notifications. The steps to manually enable notifications vary by platform. Use the following information to build a helpful message or a link to system settings.

  • macOS: System Settings → Notifications → find your app → toggle “Allow Notifications” on.
  • Windows: Settings → System → Notifications → find your app in the list and turn it on.
  • Android: Long-press the app icon → App Info → Notifications → enable “All notifications”.
  • iOS: Settings → Notifications → select your app → toggle “Allow Notifications”. You can use a fallback UI like the one shown in the component above, or even direct the user to open the relevant settings page if your app has that capability. Tauri does not currently provide a built-in way to open the OS notification settings, but you can use the shell plugin to open platform-specific URLs if they exist (e.g., on Android, an intent to open app details). For most desktop cases, guiding the user with text is sufficient.

Common Mistakes and Pitfalls

Several recurring issues catch developers who work with notification permissions for the first time. Avoiding these will save you from confusing behavior and frustrated users.

Relying on isPermissionGranted after manual toggle on macOS:

As mentioned earlier, the macOS permission state can become stale. If your logic only ever checks isPermissionGranted once at startup and then assumes it stays accurate, you may keep a “notifications disabled” message on screen even after the user re-enables them in System Settings — or vice versa. Consider periodically re-checking or using a separate mechanism (like a user-triggered refresh) if your app needs to react to changes.

  • Requesting permission immediately on launch without a user gesture. This not only violates platform guidelines but will also result in the permission being denied silently on mobile platforms. Always request in response to a deliberate click.
  • Not handling the 'denied' state at all. If the user says no, your UI should change accordingly — disable notification-related features and explain why. Pretending nothing happened confuses users.
  • Assuming requestPermission can be called multiple times to keep prompting. On most platforms, the dialog appears exactly once. After denial, further calls resolve to 'denied' without any UI. Spamming the call achieves nothing.
  • Forgetting to check permission before sending. Calling sendNotification when permission is denied will not throw an error, but the notification won’t appear. Checking first lets you avoid silent failures and provide appropriate feedback.

Best Practices

To create a respectful and effective permission experience, follow these guidelines.

  • Explain the value before asking. Use an in-app screen or tooltip that tells the user what kind of notifications they will receive and why those are useful. A button labeled “Enable deadline reminders” converts better than a generic “Allow notifications.”
  • Request permission at the right moment. Don’t ask during onboarding. Wait until the user reaches a point where notifications become relevant — for example, after they create their first task that has a due date.
  • Only ask once. After a denial, respect it. Offer a fallback that directs the user to system settings if they change their mind later.
  • Test on every platform you support. The permission dialog looks different on each OS. Run your app on macOS, Windows, and Android at minimum to confirm the prompt appears correctly and the correct permission string is used. (iOS requires a real device for notification testing.)
  • Keep the plugin and its permissions up to date. Tauri’s capability system and the notification plugin may evolve. When upgrading, review the default permission set and any new platform-specific requirements.

Summary

Notification permissions are the gatekeeper between your app and the user’s attention. Tauri v2 gives you a simple, cross-platform API through isPermissionGranted and requestPermission, but the underlying OS behavior differs significantly. Checking permission first, requesting only after a user gesture, and handling denial gracefully are the three pillars of a robust permission flow. With the complete React example and the platform insights in this guide, you can implement a permission dialog that works reliably across desktop and mobile.