Contribute

Edit this documentation

Translate the documentation or open the complete project on your computer.

Nothing is published until you choose to submit your work.

Notifications

Display native system notifications with action buttons and text input

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:

go
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:

go
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:

go
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:

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

go
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:

go
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.

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

  • macOS — Name 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.

go
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:

go
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.

go
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:

go
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).

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

go
notifier.UpdateNotification(notifications.NotificationOptions{
    ID:    "download-id",
    Title: "Download complete",
    Body:  "report.pdf is ready",
})

Per-platform behaviour:

  • macOS — UNUserNotificationCenter 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
  1. Provide clear and concise notifications:
  • Use descriptive titles, subtitles, text, and action titles
  1. Handle notification responses appropriately:
  • Check for errors in notification responses
  • Provide feedback for user actions
  1. Consider platform conventions:
  • Follow platform-specific notification patterns
  • Respect system settings
  1. 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

go
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

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

NotificationAttachment

go
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

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

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

NotificationCategory

go
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

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

NotificationResponse

go
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

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

Last updated: