AlarmKit: System-Level Alarms That Break Through Silent Mode


Your meditation app’s session-complete sound plays to an empty room because the user’s iPhone is on Silent. Your fitness app’s interval timer notification arrives three seconds late — or gets swallowed by a Focus filter entirely. Until iOS 26, only Apple’s Clock app could punch through these barriers. That monopoly is over.

AlarmKit is a brand-new framework introduced at WWDC25 that gives third-party apps first-class access to the system alarm infrastructure. You get schedule-based alarms, countdown timers, Silent-mode override, Focus filter bypass, Dynamic Island integration, and Apple Watch support — all through a clean Swift API. This post covers the full API surface, practical integration patterns, and the guardrails Apple put in place. We will not cover building a full alarm app UI (that is a separate tutorial) or the companion ClockKit complications API.

Contents

The Problem

Consider a production scenario: you are building a cooking timer for a Pixar-themed recipe app. A user sets a 12-minute timer for Remy’s ratatouille reduction and walks away from the kitchen. Your app fires a UNNotificationRequest with a UNTimeIntervalNotificationTrigger. The phone is on Silent. The notification banner appears — silently. The sauce burns.

// The old approach — fragile and easily silenced
import UserNotifications

func scheduleRatatouilleTimer() async throws {
    let content = UNMutableNotificationContent()
    content.title = "Remy's Timer"
    content.body = "Your ratatouille reduction is ready!"
    content.sound = .default // ← Silenced when the phone is muted

    let trigger = UNTimeIntervalNotificationTrigger(
        timeInterval: 12 * 60,
        repeats: false
    )

    let request = UNNotificationRequest(
        identifier: "ratatouille-timer",
        content: content,
        trigger: trigger
    )

    try await UNUserNotificationCenter.current().add(request)
}

This approach has three fundamental limitations:

  1. Silent mode kills the sound. UNNotificationContent.sound respects the hardware mute switch. There is no sanctioned way around it.
  2. Focus filters suppress delivery. If the user has a “Cooking” Focus that does not allowlist your app, the notification never surfaces.
  3. No persistent UI. The notification banner disappears after a few seconds. There is no ongoing countdown visible on the Lock Screen or Dynamic Island.

UNNotificationRequest was designed for informational alerts, not time-critical alarms. Apple recognized this gap and built AlarmKit to fill it.

What Is AlarmKit?

AlarmKit is an iOS 26+ framework that exposes the same alarm infrastructure Apple’s Clock app has used internally for years. It provides two alarm paradigms — schedule-based and countdown-based — both of which can override Silent mode and pierce Focus filters with explicit user authorization.

The framework is built around three core types:

  • AlarmManager — The entry point. You use it to schedule, query, and cancel alarms. One shared instance per app.
  • AlarmSchedule — Defines when an alarm fires: a specific date, a recurring weekday pattern, or a countdown duration.
  • AlarmConfiguration — Defines how an alarm presents: sound, haptics, snooze behavior, and Dynamic Island content.

Note: AlarmKit requires iOS 26 or later. You must add the com.apple.developer.alarmkit entitlement to your app and request user authorization at runtime before scheduling any alarm.

To get started, add AlarmKit to your target and request authorization:

import AlarmKit

@available(iOS 26, *)
func requestAlarmAccess() async throws -> Bool {
    let manager = AlarmManager.shared
    let status = try await manager.requestAuthorization()
    return status == .authorized
}

The authorization prompt is distinct from notification permissions. Users grant alarm access separately, and the system explains that alarms will sound even when the device is muted. This transparency is intentional — Apple gates the Silent-mode override behind informed consent.

Apple Docs: AlarmManager — AlarmKit

Schedule-Based Alarms

Schedule-based alarms fire at a specific time or on a recurring pattern. This is the paradigm you would use for daily wake-up alarms, weekly medication reminders, or any alarm tied to clock time.

One-Time Alarms

The simplest case: fire once at a specific date and time.

import AlarmKit

@available(iOS 26, *)
func scheduleWoodyMorningCall() async throws {
    let manager = AlarmManager.shared

    // Fire tomorrow at 6:30 AM in the user's local time zone
    var components = Calendar.current.dateComponents(
        [.year, .month, .day], from: .now.addingTimeInterval(86400)
    )
    components.hour = 6
    components.minute = 30

    let schedule = AlarmSchedule.oneTime(
        dateComponents: components,
        timeZone: .current
    )

    let config = AlarmConfiguration(
        title: "Rise and shine, partner!",
        sound: .named("woody-wakeup"),
        snoozeEnabled: true,
        snoozeDuration: .minutes(9)
    )

    let alarm = try await manager.schedule(
        schedule,
        configuration: config,
        identifier: "woody-morning-call"
    )

    print("Alarm scheduled: \(alarm.identifier)")
}

The AlarmSchedule.oneTime factory accepts DateComponents and a TimeZone. AlarmKit uses the system’s alarm subsystem to ensure the alarm fires at the correct local time — even if the user travels across time zones between scheduling and firing.

Recurring Alarms

For repeating alarms, use AlarmSchedule.recurring with a weekday set:

@available(iOS 26, *)
func scheduleWeekdayAlarm() async throws {
    let manager = AlarmManager.shared

    // Every weekday at 7:00 AM
    let schedule = AlarmSchedule.recurring(
        hour: 7,
        minute: 0,
        weekdays: [.monday, .tuesday, .wednesday, .thursday, .friday],
        timeZone: .current
    )

    let config = AlarmConfiguration(
        title: "Buzz Lightyear morning briefing",
        sound: .default,
        snoozeEnabled: true,
        snoozeDuration: .minutes(5)
    )

    try await manager.schedule(
        schedule,
        configuration: config,
        identifier: "weekday-briefing"
    )
}

The weekday set is a Set<Locale.Weekday>, which means it respects locale-aware weekday ordering. You can schedule a single alarm that fires on arbitrary day combinations — no need to create seven individual alarms for a daily pattern.

Tip: Use AlarmSchedule.daily(hour:minute:timeZone:) as a convenience when the alarm should fire every day. It is semantically equivalent to passing all seven weekdays to .recurring.

Countdown-Based Alarms

Countdown alarms fire after a duration elapses, regardless of clock time. This is the paradigm for cooking timers, workout intervals, meditation sessions, and Pomodoro timers.

@available(iOS 26, *)
func startRatatouilleTimer() async throws {
    let manager = AlarmManager.shared

    // 12-minute countdown for Remy's reduction
    let schedule = AlarmSchedule.countdown(
        duration: .minutes(12)
    )

    let config = AlarmConfiguration(
        title: "Remy's ratatouille is ready!",
        subtitle: "Remove from heat immediately",
        sound: .critical, // ← Breaks through Silent mode
        snoozeEnabled: false
    )

    try await manager.schedule(
        schedule,
        configuration: config,
        identifier: "ratatouille-countdown"
    )
}

A few things to note about countdown alarms:

  • The countdown survives app termination. AlarmKit hands the timer off to the system alarm daemon. Even if the user force-quits your app, the alarm fires.
  • The system tracks elapsed time accurately. Unlike Timer or DispatchSourceTimer, the countdown is not subject to process suspension or CPU throttling.
  • You can query remaining time. Call manager.alarm(for: "ratatouille-countdown") to get the current Alarm object, which exposes a remainingDuration property.
@available(iOS 26, *)
func checkCountdown() async throws {
    let manager = AlarmManager.shared

    if let alarm = try await manager.alarm(
        for: "ratatouille-countdown"
    ) {
        let remaining = alarm.remainingDuration
        print("Time left: \(remaining.formatted())")
    }
}

Breaking Through Silent Mode and Focus

This is the headline feature. AlarmKit alarms can sound at full volume even when the device is muted and even when a Focus filter is active. But Apple does not hand this power out unconditionally.

The Authorization Model

AlarmKit uses a three-tier authorization model:

  1. .notDetermined — The app has not yet asked for alarm access.
  2. .authorized — The user granted alarm access. Alarms can break through Silent mode.
  3. .denied — The user denied access. Alarms behave like standard notifications.
@available(iOS 26, *)
func checkAlarmAuthorization() async throws {
    let manager = AlarmManager.shared
    let status = manager.authorizationStatus

    switch status {
    case .notDetermined:
        let result = try await manager.requestAuthorization()
        print("User responded: \(result)")
    case .authorized:
        print("Alarms will break through Silent mode")
    case .denied:
        print("Alarms will respect Silent mode")
    @unknown default:
        break
    }
}

Sound Levels

AlarmKit provides three sound levels through the AlarmSound type:

Sound LevelSilent ModeFocus FiltersUse Case
.defaultRespects muteMay be filteredLow-priority reminders
.prominentOverrides muteMay be filteredStandard alarms
.criticalOverrides muteBypasses filtersSafety-critical timers

The .critical level requires an additional com.apple.developer.alarmkit.critical entitlement, which Apple reviews manually — similar to the existing Critical Alerts entitlement for UNNotificationSound.defaultCritical. Apple’s guidance from WWDC25 session Wake Up to the AlarmKit API is clear: .critical is reserved for scenarios where missing the alarm could cause physical harm (a medication reminder, a food safety timer). Do not request it for a Pomodoro app.

Warning: Submitting an app with the critical alarm entitlement for non-safety use cases will result in App Review rejection. Apple is explicit about this in the session and the entitlement request form.

Focus Filter Integration

When the user has a Focus active, .prominent alarms appear on screen but may not play sound if the Focus is configured to silence notifications. .critical alarms bypass Focus entirely — they appear, play sound, and trigger haptics regardless of any Focus configuration.

For .prominent alarms, you can improve the user experience by adopting the AlarmFocusFilterIntent protocol. This lets the user configure per-Focus alarm behavior in Settings:

import AlarmKit
import AppIntents

@available(iOS 26, *)
struct CookingAlarmFocusFilter: AlarmFocusFilterIntent {
    static var title: LocalizedStringResource = "Cooking Alarms"
    static var description: LocalizedStringResource =
        "Allow cooking timer alarms during this Focus"

    @Parameter(title: "Allow cooking alarms")
    var allowCookingAlarms: Bool

    func perform() async throws -> some IntentResult {
        AlarmManager.shared.setFocusOverride(
            enabled: allowCookingAlarms,
            forCategory: "cooking"
        )
        return .result()
    }
}

Dynamic Island Integration

Active countdown alarms automatically get a compact Dynamic Island presentation. The system provides a default countdown timer display, but you can customize it by providing a LiveActivityConfiguration in your widget extension.

import WidgetKit
import AlarmKit

@available(iOS 26, *)
struct CookingTimerLiveActivity: Widget {
    var body: some WidgetConfiguration {
        AlarmActivityConfiguration(
            for: AlarmAttributes.self
        ) { context in
            // Expanded presentation
            VStack {
                Text(context.state.dishName)
                    .font(.headline)
                Text(context.state.endDate, style: .timer)
                    .font(.system(.largeTitle, design: .monospaced))
            }
            .padding()
        } compactLeading: { context in
            Image(systemName: "flame.fill")
                .foregroundStyle(.orange)
        } compactTrailing: { context in
            Text(context.state.endDate, style: .timer)
                .foregroundStyle(.orange)
        } minimal: { context in
            Image(systemName: "flame.fill")
                .foregroundStyle(.orange)
        }
    }
}

AlarmKit provides AlarmAttributes, a system-defined ActivityAttributes type that carries the alarm’s title, subtitle, remaining duration, and state. You conform to this protocol in your widget extension to customize how the alarm looks on the Dynamic Island and Lock Screen.

When a countdown alarm is active, the Dynamic Island shows the remaining time and updates in real-time without any background processing from your app. The system handles the countdown rendering — your widget extension only defines the layout.

Tip: If you do not provide a custom AlarmActivityConfiguration, the system uses a default presentation that shows your alarm’s title and a countdown timer. This default is serviceable for many apps — you only need a custom layout if you want branding or additional context.

Apple Watch Support

AlarmKit works on watchOS 26 as well. When an alarm is scheduled on the paired iPhone, it automatically mirrors to Apple Watch. The watch will tap the user’s wrist with haptics even if the phone is in another room.

import AlarmKit

@available(iOS 26, watchOS 26, *)
func scheduleMeditationAlarm() async throws {
    let manager = AlarmManager.shared

    let schedule = AlarmSchedule.countdown(
        duration: .minutes(20)
    )

    let config = AlarmConfiguration(
        title: "Meditation complete",
        subtitle: "Inner Peace achieved, like Master Oogway",
        sound: .named("singing-bowl"),
        hapticPattern: .gentle, // ← Watch-specific haptic
        mirrorToWatch: true     // ← Explicit opt-in
    )

    try await manager.schedule(
        schedule,
        configuration: config,
        identifier: "meditation-session"
    )
}

A few details about watch mirroring:

  • mirrorToWatch: true is opt-in. Not every alarm makes sense on the wrist. A cooking timer in a kitchen app probably does; a daily standup reminder probably does not.
  • Haptic patterns are configurable via AlarmHapticPattern. Options include .standard, .gentle, .urgent, and .custom(url:) for apps that ship their own Core Haptics .ahap files.
  • Standalone watchOS apps can schedule alarms directly on the watch without a paired iPhone. The API surface is identical.

Advanced Usage

Managing Multiple Alarms

Production apps often manage dozens of concurrent alarms. AlarmKit provides batch operations:

@available(iOS 26, *)
func managePixarStudioAlarms() async throws {
    let manager = AlarmManager.shared

    // Fetch all active alarms
    let activeAlarms = try await manager.allAlarms()
    print("Active alarms: \(activeAlarms.count)")

    // Cancel a specific alarm
    try await manager.cancel(identifier: "woody-morning-call")

    // Cancel all alarms matching a predicate
    try await manager.cancelAll { alarm in
        alarm.configuration.title.contains("Remy")
    }

    // Cancel everything
    try await manager.cancelAll()
}

Observing Alarm State Changes

You can observe alarm events using an AsyncSequence:

@available(iOS 26, *)
func observeAlarmEvents() async throws {
    let manager = AlarmManager.shared

    for await event in manager.alarmEvents {
        switch event {
        case .fired(let alarm):
            print("Alarm fired: \(alarm.identifier)")
        case .snoozed(let alarm, let snoozeEnd):
            print("Snoozed until \(snoozeEnd.formatted())")
        case .dismissed(let alarm):
            print("User dismissed: \(alarm.identifier)")
        case .cancelled(let alarm):
            print("Programmatically cancelled: \(alarm.identifier)")
        @unknown default:
            break
        }
    }
}

This AsyncSequence delivers events whether your app is in the foreground or background. If your app is suspended, events are buffered and delivered when the app resumes. This is how you update your UI, log analytics, or trigger follow-up actions when an alarm fires.

Updating a Scheduled Alarm

You cannot mutate an Alarm directly. Instead, reschedule with the same identifier:

@available(iOS 26, *)
func postponeAlarm() async throws {
    let manager = AlarmManager.shared

    // Rescheduling with the same identifier replaces the existing alarm
    let newSchedule = AlarmSchedule.oneTime(
        dateComponents: DateComponents(hour: 7, minute: 30),
        timeZone: .current
    )

    let config = AlarmConfiguration(
        title: "Buzz Lightyear morning briefing — updated",
        sound: .prominent,
        snoozeEnabled: true,
        snoozeDuration: .minutes(10)
    )

    try await manager.schedule(
        newSchedule,
        configuration: config,
        identifier: "weekday-briefing" // ← Same ID replaces old alarm
    )
}

Warning: Rescheduling an alarm that is currently firing (the user sees the alarm screen) will dismiss the current alarm and schedule the new one. Handle this edge case in your app logic.

Performance Considerations

AlarmKit is deliberately lightweight. The heavy lifting happens in a system daemon, not in your process.

  • Memory footprint: AlarmKit objects are thin wrappers around XPC handles. An Alarm struct is under 200 bytes. You can hold thousands in memory without concern.
  • Scheduling latency: manager.schedule(...) round-trips to the alarm daemon via XPC. Expect 5-15ms per call. For batch scheduling (setting up 30 weekly alarms at once), schedule sequentially — there is no batch API, but the per-call cost is low enough that it completes in under a second.
  • No background runtime cost. Once an alarm is scheduled, your app does not need to run. The system daemon owns the countdown and firing. This is fundamentally different from BGTaskScheduler approaches that require your app to wake up.
  • Battery impact: Negligible. The alarm daemon is shared infrastructure that runs regardless of whether your app uses it. You are adding entries to an existing system, not spinning up new processes.

Apple Docs: AlarmSchedule — AlarmKit

When to Use (and When Not To)

AlarmKit is powerful, but it is not a replacement for every time-based notification. Here is a decision framework:

ScenarioRecommendation
Cooking timers, fitness intervalsUse AlarmKit countdown. Must be heard on Silent.
Daily wake-up or medication remindersUse AlarmKit schedule. Alarm-grade reliability.
Meditation session-end chimesAlarmKit countdown with .prominent sound.
Marketing push notificationsUNNotificationRequest. Not an alarm.
Calendar event remindersEventKit or UNNotificationRequest.
Background data sync triggersBGTaskScheduler. AlarmKit is user-facing.
Pomodoro / productivity timersAlarmKit countdown. End-of-session must be audible.

Apple’s review guidelines (referenced in WWDC25 session Wake Up to the AlarmKit API) state that AlarmKit is intended for “time-sensitive alerts where the user explicitly expects to be interrupted.” Apps that use AlarmKit for promotional or non-urgent purposes will be rejected.

Tip: If you are unsure whether your use case qualifies, ask yourself: “Would the user be upset if they missed this alert because their phone was on Silent?” If yes, AlarmKit is appropriate. If no, stick with UNNotificationRequest.

Summary

  • AlarmKit is a new iOS 26 framework that gives third-party apps access to system-level alarm infrastructure previously reserved for Apple’s Clock app.
  • Schedule-based alarms fire at specific times or on recurring weekday patterns, surviving time zone changes and device restarts.
  • Countdown-based alarms fire after a duration elapses, managed by a system daemon that is immune to app termination and CPU throttling.
  • Silent mode and Focus override is gated behind explicit user authorization and tiered sound levels (.default, .prominent, .critical).
  • Dynamic Island integration is automatic for countdown alarms, with optional custom layouts via AlarmActivityConfiguration.
  • Apple Watch mirroring is opt-in per alarm, with configurable haptic patterns.

AlarmKit fills a real gap in the platform. If your app has ever worked around Silent mode with audio session hacks or begged users to turn off Do Not Disturb, this framework is the clean, sanctioned replacement. For a hands-on walkthrough of building a complete alarm app with AlarmKit, check out Build an Alarm App with AlarmKit and SwiftUI.