Notifications
Introduction
Section titled “Introduction”Wails provides a comprehensive cross-platform notification system for desktop applications. This service allows you to display native system notifications, with support for:
- Basic notifications with title, subtitle, and body
- Interactive notifications with action buttons and text replies
- Reusable notification categories for actions
- Custom sounds (default, silent, or named)
- Attachments (images on every platform; audio/video on macOS)
- Grouping related notifications by
ThreadID - Priority via
InterruptionLevel(passive/active/timeSensitive/critical) - Scheduled delivery (native on macOS; in-process timer on Windows + Linux)
- Updating an in-flight notification by ID
Each new optional field gracefully degrades when a platform cannot honour it; see Platform Considerations for the per-feature support matrix.
Basic Usage
Section titled “Basic Usage”Creating the Service
Section titled “Creating the Service”First, initialize the notifications service:
import "github.com/wailsapp/wails/v3/pkg/application"import "github.com/wailsapp/wails/v3/pkg/services/notifications"
// Create a new notification servicenotifier := notifications.New()
//Register the service with the applicationapp := application.New(application.Options{ Services: []application.Service{ application.NewService(notifier), },})Notification Authorization
Section titled “Notification Authorization”Notifications on macOS require user authorization. Request and check authorization:
authorized, err := notifier.CheckNotificationAuthorization()if err != nil { // Handle authorization error}if authorized { // Send notifications} else { // Request authorization authorized, err = notifier.RequestNotificationAuthorization()}On Windows and Linux this always returns true.
Notification Types
Section titled “Notification Types”Basic Notifications
Section titled “Basic Notifications”Send a basic notification with a unique id, title, optional subtitle (macOS and Linux), and body text to users:
notifier.SendNotification(notifications.NotificationOptions{ ID: "unique-id", Title: "New Calendar Invite", Subtitle: "From: Jane Doe", // Optional Body: "Tap to view the event",})Interactive Notifications
Section titled “Interactive Notifications”Send a notification with action buttons and text inputs. These notifications require a notification category to be resgistered first:
// Define a unique category idcategoryID := "unique-category-id"
// Define a category with actionscategory := notifications.NotificationCategory{ ID: categoryID, Actions: []notifications.NotificationAction{ { ID: "OPEN", Title: "Open", }, { ID: "ARCHIVE", Title: "Archive", Destructive: true, /* macOS-specific */ }, }, HasReplyField: true, ReplyPlaceholder: "message...", ReplyButtonTitle: "Reply",}
// Register the categorynotifier.RegisterNotificationCategory(category)
// Send an interactive notification with the actions registered in the provided categorynotifier.SendNotificationWithActions(notifications.NotificationOptions{ ID: "unique-id", Title: "New Message", Subtitle: "From: Jane Doe", Body: "Are you able to make it?", CategoryID: categoryID,})Notification Responses
Section titled “Notification Responses”Process user interactions with notifications:
notifier.OnNotificationResponse(func(result notifications.NotificationResult) { response := result.Response fmt.Printf("Notification %s was actioned with: %s\n", response.ID, response.ActionIdentifier)
if response.ActionIdentifier == "TEXT_REPLY" { fmt.Printf("User replied: %s\n", response.UserText) }
if data, ok := response.UserInfo["sender"].(string); ok { fmt.Printf("Original sender: %s\n", data) }
// Emit an event to the frontend app.Event.Emit("notification", result.Response)})Notification Customisation
Section titled “Notification Customisation”Custom Metadata
Section titled “Custom Metadata”Basic and interactive notifications can include custom data:
notifier.SendNotification(notifications.NotificationOptions{ ID: "unique-id", Title: "New Calendar Invite", Subtitle: "From: Jane Doe", // Optional Body: "Tap to view the event", Data: map[string]interface{}{ "sender": "jane.doe@example.com", "timestamp": "2025-03-10T15:30:00Z", }})Custom Sound
Section titled “Custom Sound”Use Sound to control the audio played when a notification is delivered. Leaving it nil plays the platform default; set Silent: true to suppress sound; set Name to play a named/bundled sound.
// Silentnotifier.SendNotification(notifications.NotificationOptions{ ID: "silent-id", Title: "Background sync complete", Sound: ¬ifications.NotificationSound{Silent: true},})
// Named soundnotifier.SendNotification(notifications.NotificationOptions{ ID: "named-id", Title: "New message", Body: "Tap to read", Sound: ¬ifications.NotificationSound{Name: "Ping"},})How Name is resolved per platform:
- macOS —
Nameis passed to[UNNotificationSound soundNamed:]; the audio file must live under your app bundle’sLibrary/Sounds. - Windows — if
Namealready begins withms-winsoundevent:orms-appx:, it is used as-is; otherwise it is wrapped inms-winsoundevent:for use as a built-in toast event name (see Microsoft’s toast<audio>schema documentation). - Linux — forwarded as the freedesktop
sound-namehint; whether it plays depends on the active notification daemon and sound theme.
Attachments
Section titled “Attachments”Attachments adds media files alongside a notification. macOS supports multiple attachments of any media type; Windows and Linux honour the first image-typed attachment.
notifier.SendNotification(notifications.NotificationOptions{ ID: "image-id", Title: "Photo uploaded", Body: "Tap to view", Attachments: []notifications.NotificationAttachment{ { ID: "preview", Path: "/absolute/path/to/image.png", // Type hint: // macOS: UTI like "public.png" / "public.audio" (often inferred) // Windows: "hero" | "appLogoOverride" | "inline" (default "inline") // Linux: ignored Type: "inline", }, },})Path must be an absolute filesystem path. macOS additionally accepts file:// URLs.
Attaching a file that ships with your app
Section titled “Attaching a file that ships with your app”The OS reads the attachment from disk when the notification is delivered, so Path has to resolve to a real file on the end user’s machine. For an asset you bundle with the application (an icon or image embedded with go:embed), there is no fixed absolute path you can hard-code, because the file lives inside the binary rather than at a known location on disk. Write it to a writable directory once at startup and pass that path:
import ( _ "embed" "os" "path/filepath")
//go:embed assets/preview.pngvar previewPNG []byte
// Materialise the embedded asset to a stable path the OS can read.previewPath := filepath.Join(os.TempDir(), "myapp-preview.png")if err := os.WriteFile(previewPath, previewPNG, 0o644); err != nil { // handle error}
notifier.SendNotification(notifications.NotificationOptions{ ID: "image-id", Title: "Photo uploaded", Body: "Tap to view", Attachments: []notifications.NotificationAttachment{ {ID: "preview", Path: previewPath, Type: "inline"}, },})A user-supplied or downloaded file already has a real path on disk, so you can pass it to Path directly without this step. Passing attachment bytes in-memory may be added in a future release.
Threading and Grouping
Section titled “Threading and Grouping”ThreadID groups related notifications together so the OS can collapse them in Notification Center / Action Center.
notifier.SendNotification(notifications.NotificationOptions{ ID: "msg-42", Title: "Jane Doe", Body: "Are you free for lunch?", ThreadID: "chat:jane.doe",})Interruption Level
Section titled “Interruption Level”InterruptionLevel controls notification priority. Use one of the exported constants:
notifier.SendNotification(notifications.NotificationOptions{ ID: "alert-id", Title: "Server down", Body: "Investigate immediately", InterruptionLevel: notifications.InterruptionLevelTimeSensitive,})| Constant | Value | Meaning |
|---|---|---|
InterruptionLevelPassive | "passive" | Quiet delivery; will not light up the screen or play a default sound |
InterruptionLevelActive | "active" | Default level |
InterruptionLevelTimeSensitive | "timeSensitive" | Breaks through Focus / Do Not Disturb where allowed |
InterruptionLevelCritical | "critical" | Bypasses Focus and ringer; macOS requires the Critical Alert entitlement (silently degrades without it) |
Per-platform mapping:
- macOS — sets
UNNotificationContent.interruptionLevel.criticalrequires macOS 12+ and the Critical Alert entitlement. - Windows — maps to the toast
<toast scenario="...">attribute. - Linux — maps to the freedesktop
urgencyhint.
Scheduled Delivery
Section titled “Scheduled Delivery”Schedule defers delivery. Set exactly one of DelaySeconds (seconds from now) or At (Unix seconds, UTC).
// Deliver in 60 secondsnotifier.SendNotification(notifications.NotificationOptions{ ID: "reminder-id", Title: "Stand up and stretch", Schedule: ¬ifications.NotificationSchedule{DelaySeconds: 60},})
// Deliver at an absolute timenotifier.SendNotification(notifications.NotificationOptions{ ID: "alarm-id", Title: "Meeting in 5 minutes", Schedule: ¬ifications.NotificationSchedule{At: time.Now().Add(time.Hour).Unix()},})Updating Notifications
Section titled “Updating Notifications”UpdateNotification replaces an in-flight notification with the same ID:
notifier.UpdateNotification(notifications.NotificationOptions{ ID: "download-id", Title: "Download complete", Body: "report.pdf is ready",})Per-platform behaviour:
- macOS —
UNUserNotificationCenterauto-deduplicates by identifier, so the existing notification is updated in place. - Linux — uses the D-Bus
replaces_idparameter to replace the previous notification. - Windows — currently redelivers as a new notification. True in-place replace requires upstream
wintoastsupport fortag/group.
Platform Considerations
Section titled “Platform Considerations”On macOS, notifications:
- Require user authorization
- Require the app to be packaged and signed (notarized for distribution)
- Use system-standard notification appearances
- Support
Subtitle - Support user text input (replies)
- Support the
Destructiveaction option - Support multiple
Attachmentsof any media type (images, audio, video) - Support
ThreadIDfor grouping in Notification Center - Support all
InterruptionLevelvalues (criticalrequires the Critical Alert entitlement) - Support native scheduled delivery that survives app restarts
- Auto-deduplicate
UpdateNotificationcalls byID - Automatically handle dark/light mode
On Windows, notifications:
- Use Windows system toast styles via the
wintoastbackend - Adapt to Windows theme settings
- Support user text input (replies)
- Support high DPI displays
- Do not support
Subtitle - Support a single image
Attachmentwith placement hinthero,appLogoOverride, orinline(defaultinline) - Support
ThreadIDfor grouping in Action Center - Support
InterruptionLevelvia the toastscenarioattribute - Support scheduled delivery via an in-process timer — scheduled notifications are lost if the app exits before delivery
UpdateNotificationcurrently redelivers as a new notification (true in-place replace pending upstreamwintoasttag/groupsupport)
On Linux, notifications use the D-Bus org.freedesktop.Notifications interface. A compatible notification daemon must be running for notifications to work.
On Linux, notifications:
- Follow desktop environment theme
- Position according to desktop environment rules
- Support
Subtitle(concatenated into the body for daemons that don’t render it separately) - Do not support user text input (not part of the freedesktop spec)
- Support a single image
Attachmentvia theimage-pathhint - Support
ThreadID(handled by the daemon where supported) Sound.Nameis forwarded as thesound-namehint; whether it plays depends on the active daemon and sound theme- Map
InterruptionLevelto the freedesktopurgencyhint - Support scheduled delivery via an in-process timer — scheduled notifications are lost if the app exits before delivery
UpdateNotificationuses the D-Busreplaces_idparameter to replace the previous notification in place
Best Practices
Section titled “Best Practices”-
Check and request for authorization:
- macOS requires user authorization
-
Provide clear and concise notifications:
- Use descriptive titles, subtitles, text, and action titles
-
Handle notification responses appropriately:
- Check for errors in notification responses
- Provide feedback for user actions
-
Consider platform conventions:
- Follow platform-specific notification patterns
- Respect system settings
-
On Linux, handle the daemon dependency:
- Check the error returned by
SendNotification— a missing daemon produces a D-Bus error - Package docs or app README should note that a freedesktop notification daemon is required
- Check the error returned by
Examples
Section titled “Examples”Explore this example:
API Reference
Section titled “API Reference”Service Management
Section titled “Service Management”| Method | Description |
|---|---|
New() | Creates a new notifications service |
Notification Authorization
Section titled “Notification Authorization”| Method | Description |
|---|---|
RequestNotificationAuthorization() | Requests permission to display notifications (macOS) |
CheckNotificationAuthorization() | Checks current notification authorization status (macOS) |
Sending Notifications
Section titled “Sending Notifications”| Method | Description |
|---|---|
SendNotification(options NotificationOptions) | Sends a basic notification |
SendNotificationWithActions(options NotificationOptions) | Sends an interactive notification with actions |
UpdateNotification(options NotificationOptions) | Updates an in-flight notification by ID (see Updating Notifications) |
Notification Categories
Section titled “Notification Categories”| Method | Description |
|---|---|
RegisterNotificationCategory(category NotificationCategory) | Registers a reusable notification category |
RemoveNotificationCategory(categoryID string) | Removes a previously registered category |
Managing Notifications
Section titled “Managing Notifications”| Method | Description |
|---|---|
RemoveAllPendingNotifications() | Removes all pending notifications (macOS and Linux only) |
RemovePendingNotification(identifier string) | Removes a specific pending notification (macOS and Linux only) |
RemoveAllDeliveredNotifications() | Removes all delivered notifications (macOS and Linux only) |
RemoveDeliveredNotification(identifier string) | Removes a specific delivered notification (macOS and Linux only) |
RemoveNotification(identifier string) | Removes a notification (Linux-specific) |
Event Handling
Section titled “Event Handling”| Method | Description |
|---|---|
OnNotificationResponse(callback func(result NotificationResult)) | Registers a callback for notification responses |
Structs and Types
Section titled “Structs and Types”NotificationOptions
Section titled “NotificationOptions”type NotificationOptions struct { ID string // Unique identifier for the notification (required) Title string // Main notification title (required) Subtitle string // Subtitle text (macOS and Linux only) Body string // Main notification content CategoryID string // Category identifier for interactive notifications Data map[string]interface{} // Custom data to associate with the notification
// Sound controls the sound played on delivery. nil = platform default. Sound *NotificationSound
// Attachments are media files shown alongside the notification. // macOS supports multiple attachments of any media type; // Windows and Linux honour the first image-typed attachment. Attachments []NotificationAttachment
// ThreadID groups related notifications together in // Notification Center / Action Center / the Linux notification daemon. ThreadID string
// InterruptionLevel controls priority. One of "passive", // "active" (default), "timeSensitive", "critical". InterruptionLevel string
// Schedule defers delivery. macOS uses a native trigger that survives // restarts; Windows and Linux use an in-process timer that does NOT. Schedule *NotificationSchedule}NotificationSound
Section titled “NotificationSound”type NotificationSound struct { Silent bool // If true, no sound is played Name string // Named/bundled sound (see "Custom Sound" above)}NotificationAttachment
Section titled “NotificationAttachment”type NotificationAttachment struct { ID string // Optional identifier Path string // Absolute filesystem path (macOS also accepts file:// URLs) // Type is an optional placement/UTI hint: // macOS: UTI like "public.png" / "public.audio" (often inferred) // Windows: "hero" | "appLogoOverride" | "inline" (default "inline") // Linux: ignored (always image-path hint) Type string}NotificationSchedule
Section titled “NotificationSchedule”// Exactly one of DelaySeconds or At must be set. At is Unix seconds (UTC).type NotificationSchedule struct { DelaySeconds int // Seconds from now until delivery At int64 // Absolute Unix timestamp (seconds, UTC)}InterruptionLevel constants
Section titled “InterruptionLevel constants”const ( InterruptionLevelPassive = "passive" InterruptionLevelActive = "active" // default InterruptionLevelTimeSensitive = "timeSensitive" InterruptionLevelCritical = "critical")NotificationCategory
Section titled “NotificationCategory”type NotificationCategory struct { ID string // Unique identifier for the category Actions []NotificationAction // Button actions for the notification HasReplyField bool // Whether to include a text input field ReplyPlaceholder string // Placeholder text for the input field ReplyButtonTitle string // Text for the reply button}NotificationAction
Section titled “NotificationAction”type NotificationAction struct { ID string // Unique identifier for the action Title string // Button text Destructive bool // Whether the action is destructive (macOS-specific)}NotificationResponse
Section titled “NotificationResponse”type NotificationResponse struct { ID string // Notification identifier ActionIdentifier string // Action that was triggered CategoryID string // Category of the notification Title string // Title of the notification Subtitle string // Subtitle of the notification Body string // Body text of the notification UserText string // Text entered by the user UserInfo map[string]interface{} // Custom data from the notification}NotificationResult
Section titled “NotificationResult”type NotificationResult struct { Response NotificationResponse // Response data Error error // Any error that occurred}