Skip to content
← Back to Blog
🛠️

AlarmKit Explained: How iOS 26 Lets Apps Ring Real Alarms (Even in Silent Mode)

September 15, 2026 · 9 min read

This is a developer-oriented explainer written from shipping AlarmKit in a production app. API names mentioned here are the ones we actually use; everything else is described in prose on purpose. Apple's documentation is the source of truth: developer.apple.com/documentation/alarmkit.

For fifteen years, "can my app set a real alarm?" had one honest answer on iOS: no. You could schedule a local notification, attach a sound, and hope the user hadn't flipped the silent switch or turned on a Focus. That is not an alarm. That is a polite suggestion.

AlarmKit, introduced with iOS 26, changes the answer. Third-party apps can now schedule system alarms that behave like the ones in Apple's Clock app: a full-screen alert, a sound that plays through silent mode and Focus, and a Stop button that means it. This article explains what the framework is, why it is different from notifications, what you must wire up before an alarm renders at all, and the lessons we learned making an alarm ring with a user's own recorded voice. It belongs to our wake-up alarm cluster; the non-technical version is Can an Alarm Play My Own Voice?.

What AlarmKit Is

AlarmKit is a system framework, available as of iOS 26, that lets an app hand an alarm to the operating system. Once scheduled, the alarm no longer depends on your app: iOS keeps it, fires it, presents it, and plays its sound. Your process can be suspended or terminated and the alarm still rings, because the ringing is done by a system daemon, not by you.

The user-facing result is the same treatment the built-in Clock alarm gets: a full-screen alert on the Lock Screen, a Dynamic Island / Live Activity presence on supported devices, a sound that ignores the silent switch, and priority over Focus modes. It also means the user can see and manage your alarms in the same mental category as "real" alarms, rather than as notifications they might have muted months ago.

The one-line summary: notifications ask for attention. Alarms take it. AlarmKit lets a third-party app do the second thing, with the user's explicit permission.

Alarms vs. Notifications: Why the Distinction Matters

If you have ever shipped a "wake-up" feature on top of local notifications, you know the support tickets. The table below is the gap AlarmKit closes.

BehaviorLocal notificationAlarmKit alarm
Silent switch onSound is mutedSound plays
Focus / Do Not DisturbUsually suppressedBreaks through
PresentationBanner; easy to swipe awayFull-screen alert with Stop
Sound lengthCapped at 30 secondsRings until stopped
Who plays the soundSystem, but at notification prioritySystem, at alarm priority
PermissionNotification authorizationSeparate alarm authorization

Two of those rows matter more than the others. The silent switch is the one that made "alarm apps" impossible to trust: a user who silenced their phone for a meeting at 4 p.m. did not wake up at 6 a.m. And the 30-second cap on notification sounds is what made every notification-based alarm feel like a text message rather than something you must deal with.

There is a cost to the power. AlarmKit has its own authorization flow, separate from notifications, and the prompt is explicit about what the user is granting. Ask for it at the moment the user is setting an alarm, not on first launch.

The Moving Parts You Must Wire Up

The scheduling call itself is small. What surprised us is how much has to exist around it before an alarm actually appears on screen. Here is the checklist, in the order things fail if you skip them.

1. The usage description

Add NSAlarmKitUsageDescription to Info.plist. Without it the authorization request has nothing to show and the system declines. Write it for the user, not the reviewer: say what the alarm will do and why it needs to ring through silence.

2. Authorization

Authorization goes through AlarmManager.shared. You request it once and check authorizationState before every schedule call, because the user can revoke access in Settings at any time.

Simplified:

// Ask when the user taps "Add alarm", not at launch.
let state = try await AlarmManager.shared.requestAuthorization()

// Later, before scheduling anything:
guard AlarmManager.shared.authorizationState == .authorized else {
    // Explain, and offer a path to Settings.
    return
}

3. Live Activities and a widget extension

This is the step that cost us the most time, so it gets the boldest warning. As of iOS 26, the alarm's on-screen presentation is rendered by a widget extension through the Live Activity system. Your app target needs NSSupportsLiveActivities set, and you need a widget extension that declares an ActivityConfiguration for AlarmAttributes with your own metadata type.

Simplified, in the widget extension:

struct WakeUpAlarmMetadata: AlarmMetadata { /* your fields */ }

ActivityConfiguration(for: AlarmAttributes<WakeUpAlarmMetadata>.self) { context in
    // Lock Screen / Dynamic Island presentation
}

If the extension is missing, the failure mode is silent and confusing: the daemon fires the alarm on schedule, the alarm's state becomes "alerting", and nothing at all is drawn. No sound, no full-screen alert, no error. You will see the alarm in AlarmManager.shared.alarms looking perfectly healthy. In our experience the metadata struct is matched by its unqualified type name across the app and the extension, so keep the name identical in both targets.

4. The configuration

The alarm itself is described by AlarmManager.AlarmConfiguration.alarm(...), which bundles the schedule (a time plus optional repeat weekdays), the alert presentation, and the sound. Sound is an AlertConfiguration.AlertSound: .default for the system tone, or .named(...) for a sound file you ship or write yourself. More on that in the next section.

5. The alert's buttons

In the alert-only presentation we use, the full-screen alert offers Stop plus one secondary button that you define. That button runs a LiveActivityIntent. Affilist labels it "Listen": the intent opens the app and hands it a flag, and the app starts playing the user's full recording immediately, so the 29-second ringtone flows into the whole message. Note what is not there: in this configuration there is no snooze. If your product needs "five more minutes", you have to build it yourself or design around it.

6. Reconciliation

Because the system owns the alarms, your local model and the daemon's list can drift: the user deletes the app's data, a migration changes IDs, an old build scheduled something under a scheme you no longer use. On launch we read AlarmManager.shared.alarms, re-schedule anything we have stored that the daemon lacks, and cancel(id:) anything the daemon has that we do not recognize. Using your own stable UUID as the alarm ID makes this trivial; letting the system pick IDs makes it a chore.

Custom Sounds and the "Own Voice" Trick

Affilist's whole reason for adopting AlarmKit is that the alarm rings with the user's own recorded voice. Here is how that works and where the edges are.

The sound has to be a file the system can read. We write the user's recording into the app's Library/Sounds directory as an .m4a named after the alarm's ID, then reference it with .named(...). That directory is the same convention notification sounds have used for years, which is a reasonable hint about the lineage.

We cap it at 29 seconds. Alarm sounds loop until stopped, so a 29-second clip rings indefinitely anyway; keeping it short also keeps the file small and the export fast. For a single manifesto recording we trim to the cap. For a playlist alarm we stitch the individual affirmation recordings in playlist order with AVMutableComposition and export with AVAssetExportSession, stopping when the next clip would cross the cap.

Always have a fallback. If the file is missing, malformed, or rejected, we schedule with .default rather than failing the alarm. A wake-up alarm that plays the wrong sound is a bug; a wake-up alarm that does not ring is a disaster. Our UI also warns in the alarm sheet when the target has no recording yet, so the user knows the standard tone will play.

Keep the file fresh. Whenever a recording changes (the user re-records, or AI-generated audio arrives for an affirmation that had none) we regenerate the ringtone and re-schedule. Before we hooked the AI-audio path into that refresh, an alarm created during onboarding kept the default sound forever, because at scheduling time there was no recording yet.

Give the user a privacy switch. Every alarm target has a "Wake up to my voice" toggle. Off means the alarm uses the standard sound and the voice file is deleted from Library/Sounds. People share bedrooms; the feature should never be a surprise to a partner.

Lessons From a Shipping App

Beyond the mechanics, a few product decisions came directly from watching real alarms fire.

Refuse two alarms at the same minute

If two system alarms fire simultaneously, both ring. Tapping Stop on the visible one leaves the other ringing, and users read that as "Stop didn't work" and one-star the app. Our validation rejects a new alarm whose hour:minute and weekdays overlap an existing one, both within the same playlist and across every other playlist and the manifesto. The check scans every stored alarm record before allowing the add.

Suggest a backup, cap the total

Since there is no snooze in this presentation, we nudge users with exactly one alarm to add a second one a few minutes later as a safety net, and we cap alarms at five per target so a playlist cannot quietly accumulate a dozen. Add and delete only, no in-place editing: fewer states, fewer reconciliation bugs.

Stop your own ringing on activation

When the app becomes active for any reason, we stop any of our alarms that are still alerting. If the user got to the app, they are awake; a ringtone continuing under the UI is just noise.

Model multiple alarms per target from day one

We shipped with one alarm per playlist, then added several. That meant decoding legacy single-alarm payloads, cancelling the old daemon alarms that had been keyed by target ID, and re-scheduling under per-alarm IDs. It works, but it is the kind of migration you would rather not write. If you think you might ever need more than one alarm per thing, use an array from the start.

What AlarmKit Does Not Do (Yet)

A few honest boundaries, as of iOS 26 and as far as our usage goes:

Everything above reflects what we needed to ship a voice wake-up alarm. Apple's framework is broader than our use of it; for the full surface, current signatures, and any changes after this was written, read the official AlarmKit documentation. For the user-side companion pieces, see why a normal iPhone alarm rings in silent mode and app alarms usually don't and how to set a custom voice alarm on iPhone.

Frequently Asked Questions

Does AlarmKit work on iOS 18 or earlier?

No. AlarmKit is new in iOS 26, and an app built around it has to require iOS 26 or provide a separate, weaker path (local notifications) for older systems. Affilist requires iOS 26 for exactly this reason.

Can an AlarmKit alarm use any sound file?

It can use a sound file the system can read, referenced by name; in our app that is an .m4a written to the Library/Sounds directory, capped at 29 seconds. If the system rejects the file, schedule with the default sound instead of failing.

Why does my alarm fire but show nothing?

Almost always because there is no widget extension declaring an ActivityConfiguration for AlarmAttributes with your metadata type, or the app lacks NSSupportsLiveActivities. The daemon fires the alarm and marks it alerting, but nothing is rendered and no sound plays.

Is there a snooze button?

Not in the alert-only presentation we use: the alert has Stop plus one custom button that runs a LiveActivityIntent. If your product needs snooze-like behavior, design it yourself, for example by suggesting a second alarm a few minutes later.

See AlarmKit in the Wild

Affilist is a shipping iOS 26 app built on everything above: record affirmations or a personal manifesto in your own voice, and wake up to a real alarm that plays them — even in silent mode.

Download Affilist Free