Notifications

Introduction

Wails provides a comprehensive cross-platform notification system for desktop applications. This service allows you to display native system notifications, with support for:

Each new optional field gracefully degrades when a platform cannot honour it; see Platform Considerations for the per-feature support matrix.

Basic Usage

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 service
notifier := notifications.New()

//Register the service with the application
app := application.New(application.Options{
    Services: []application.Service{
        application.NewService(notifier),
    },
})

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

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

Send a notification with action buttons and text inputs. These notifications require a notification category to be resgistered first:

// Define a unique category id
categoryID := "unique-category-id"

// Define a category with actions
category := 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 category
notifier.RegisterNotificationCategory(category)

// Send an interactive notification with the actions registered in the provided category
notifier.SendNotificationWithActions(notifications.NotificationOptions{
    ID:         "unique-id",
    Title:      "New Message",
    Subtitle:   "From: Jane Doe",
    Body:       "Are you able to make it?",
    CategoryID: categoryID,
})

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

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

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.

// Silent
notifier.SendNotification(notifications.NotificationOptions{
    ID:    "silent-id",
    Title: "Background sync complete",
    Sound: &notifications.NotificationSound{Silent: true},
})

// Named sound
notifier.SendNotification(notifications.NotificationOptions{
    ID:    "named-id",
    Title: "New message",
    Body:  "Tap to read",
    Sound: &notifications.NotificationSound{Name: "Ping"},
})

How Name is resolved per platform:

  • macOSName is passed to [UNNotificationSound soundNamed:]; the audio file must live under your app bundle’s Library/Sounds.
  • Windows — if Name already begins with ms-winsoundevent: or ms-appx:, it is used as-is; otherwise it is wrapped in ms-winsoundevent: for use as a built-in toast event name (see Microsoft’s toast <audio> schema documentation).
  • Linux — forwarded as the freedesktop sound-name hint; whether it plays depends on the active notification daemon and sound theme.

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

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.png
var 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

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

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. critical requires macOS 12+ and the Critical Alert entitlement.
  • Windows — maps to the toast <toast scenario="..."> attribute.
  • Linux — maps to the freedesktop urgency hint.

Scheduled Delivery

Schedule defers delivery. Set exactly one of DelaySeconds (seconds from now) or At (Unix seconds, UTC).

// Deliver in 60 seconds
notifier.SendNotification(notifications.NotificationOptions{
    ID:       "reminder-id",
    Title:    "Stand up and stretch",
    Schedule: &notifications.NotificationSchedule{DelaySeconds: 60},
})

// Deliver at an absolute time
notifier.SendNotification(notifications.NotificationOptions{
    ID:       "alarm-id",
    Title:    "Meeting in 5 minutes",
    Schedule: &notifications.NotificationSchedule{At: time.Now().Add(time.Hour).Unix()},
})

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:

  • macOSUNUserNotificationCenter auto-deduplicates by identifier, so the existing notification is updated in place.
  • Linux — uses the D-Bus replaces_id parameter to replace the previous notification.
  • Windows — currently redelivers as a new notification. True in-place replace requires upstream wintoast support for tag / group.

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 Destructive action option
  • Support multiple Attachments of any media type (images, audio, video)
  • Support ThreadID for grouping in Notification Center
  • Support all InterruptionLevel values (critical requires the Critical Alert entitlement)
  • Support native scheduled delivery that survives app restarts
  • Auto-deduplicate UpdateNotification calls by ID
  • Automatically handle dark/light mode

Best Practices

  1. Check and request for authorization:

    • macOS requires user authorization
  2. Provide clear and concise notifications:

    • Use descriptive titles, subtitles, text, and action titles
  3. Handle notification responses appropriately:

    • Check for errors in notification responses
    • Provide feedback for user actions
  4. Consider platform conventions:

    • Follow platform-specific notification patterns
    • Respect system settings
  5. 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

Examples

Explore this example:

API Reference

Service Management

Method Description
New() Creates a new notifications service

Notification Authorization

Method Description
RequestNotificationAuthorization() Requests permission to display notifications (macOS)
CheckNotificationAuthorization() Checks current notification authorization status (macOS)

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

Method Description
RegisterNotificationCategory(category NotificationCategory) Registers a reusable notification category
RemoveNotificationCategory(categoryID string) Removes a previously registered category

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

Method Description
OnNotificationResponse(callback func(result NotificationResult)) Registers a callback for notification responses

Structs and Types

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

type NotificationSound struct {
    Silent bool   // If true, no sound is played
    Name   string // Named/bundled sound (see "Custom Sound" above)
}

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

// 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

const (
    InterruptionLevelPassive       = "passive"
    InterruptionLevelActive        = "active" // default
    InterruptionLevelTimeSensitive = "timeSensitive"
    InterruptionLevelCritical      = "critical"
)

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

type NotificationAction struct {
    ID          string  // Unique identifier for the action
    Title       string  // Button text
    Destructive bool    // Whether the action is destructive (macOS-specific)
}

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

type NotificationResult struct {
    Response NotificationResponse  // Response data
    Error    error                 // Any error that occurred
}
Edit page

Last updated: