App Intents in iOS 26: Siri, Spotlight, and Apple Intelligence with SwiftUI
A practical iOS 26 guide to App Intents: write your first AppIntent, expose entities to Apple Intelligence, build interactive snippets, and ship to Siri, Spotlight, and the Action Button without an extension target.
App Intents in iOS 26 are Swift-native types that expose your app's actions to Siri, Spotlight, Shortcuts, the Action Button, Control Center, and (new this year) Apple Intelligence's on-device reasoning. You declare a struct conforming to AppIntent, list its parameters, and the system handles discovery, voice grammar, donation, and now interactive snippet UI. There's no separate intent definition file, no SiriKit domain mapping, no Objective-C bridging. I've been shipping intents since the framework debuted in iOS 16, and honestly, the iOS 26 release is the first one where I'd argue you can't ship a competitive iOS app without them.
App Intents replaced SiriKit and the legacy .intentdefinition file as the single way to expose actions to Siri, Spotlight, Shortcuts, and Apple Intelligence in iOS 26.
An intent is a Swift struct conforming to AppIntent with a perform() method; parameters use the @Parameter property wrapper with optional dialog and validation.
iOS 26 introduces interactive snippets, letting an intent return a SwiftUI view that users can tap to drive follow-up intents without opening the app.
AppEntity and EntityQuery let Apple Intelligence reason about your app's data; combined with the new @AssistantIntent macro, an intent can be exposed directly to the system assistant.
Donating intents through IntentDonationManager trains Siri Suggestions; Shortcuts authored with AppShortcutsProvider are surfaced automatically without donation.
Intents that read or mutate user data should declare openAppWhenRun, parameter summaries, and the new SupportedModes property so the system can run them in the background or foreground correctly.
What are App Intents in iOS?
An App Intent is a single Swift type that describes one user-facing action your app can perform. The framework was introduced in iOS 16 to replace the Objective-C-era SiriKit Intents system, and in iOS 26 it has absorbed nearly every system surface that used to require its own SDK: Siri, Spotlight, the Action Button, Focus filters, Shortcuts, Control Center, and the new Assistant. Internally, an intent is just a struct conforming to AppIntent with a perform() method that returns an IntentResult.
The mental model is simple. Each AppIntent is a public API endpoint for your app, callable by any system service. The framework handles serialization, voice grammar generation, parameter prompting, and execution context. You only write Swift. Coming from the Objective-C days when SiriKit required a separate intents extension target, a per-domain INIntent subclass, and a hand-written response generator, the simplification is dramatic. And the surface area you get in return is much larger.
The protocol itself is tiny: a title, an optional description, and the perform() method. Everything else (parameters, dialog, opening the app, returning a view) is opt-in through property wrappers and protocol requirements. That keeps simple intents genuinely simple while letting the complex ones scale into full Apple Intelligence integrations.
Writing your first App Intent in SwiftUI
Here's the smallest useful intent. It marks the most recent note as a favorite and returns a confirmation dialog for Siri to speak. Drop this in your main app target. No extension required for basic intents in iOS 26.
import AppIntents
import SwiftUI
struct FavoriteLatestNoteIntent: AppIntent {
static let title: LocalizedStringResource = "Favorite Latest Note"
static let description = IntentDescription(
"Marks the most recently edited note as a favorite.",
categoryName: "Notes"
)
static let openAppWhenRun = false
@Dependency private var store: NoteStore
func perform() async throws -> some IntentResult & ProvidesDialog {
guard let note = try await store.latest() else {
return .result(dialog: "You don't have any notes yet.")
}
try await store.favorite(note.id)
return .result(dialog: "Favorited \(note.title).")
}
}
Three things to notice. First, @Dependency resolves a registered service the same way SwiftUI's @Environment resolves environment values; you call AppDependencyManager.shared.add { NoteStore() } once at launch and every intent gets the same instance. Second, the return type some IntentResult & ProvidesDialog composes opt-in behaviors. Add OpensIntent, ShowsSnippetView, or ReturnsValue<T> as needed. Third, because openAppWhenRun is false, the system runs the intent in the background, so your NoteStore must be thread-safe. That's exactly why I lean on actors here. If you're new to actor isolation, the complete guide to Swift Actors covers the model the framework expects.
Parameters, summaries, and dialog
Real intents take input. The @Parameter property wrapper turns a stored property into a typed input that Siri can resolve from voice, that Shortcuts can wire to another step, and that Apple Intelligence can fill in from natural-language context.
struct CreateReminderIntent: AppIntent {
static let title: LocalizedStringResource = "Create Reminder"
@Parameter(title: "Title")
var text: String
@Parameter(title: "Due Date", kind: .dateTime)
var due: Date?
@Parameter(title: "List", default: .inbox)
var list: ReminderList
static var parameterSummary: some ParameterSummary {
Summary("Add \(\.$text) to \(\.$list)") {
\.$due
}
}
func perform() async throws -> some IntentResult & ReturnsValue<Reminder> {
let reminder = try await ReminderStore.shared.create(
text: text, due: due, list: list
)
return .result(value: reminder)
}
}
parameterSummary is the sentence that appears in the Shortcuts editor and in Siri's confirmation prompts. The trailing closure holds optional parameters that should be hidden by default and revealed under a "Show More" disclosure. Get this right and your intent reads like English in the Shortcuts UI; get it wrong and it reads like a tax form. I treat parameter summaries with the same care I'd give to a public function signature, because that's effectively what they are.
When a parameter is required and the user hasn't supplied a value, the framework prompts automatically. You can customize the prompt with @Parameter(title: "Title", requestValueDialog: "What should the reminder say?"). That string is read by Siri verbatim, so write it as a spoken question, not a form label.
App Entities and EntityQuery
For anything more interesting than primitives, you'll define an AppEntity. An entity is a stable, identifiable model: a note, a reminder, a vehicle, a chat. The framework uses entities to wire Shortcuts steps together (the output of one intent becomes the input of the next), to power Spotlight, and in iOS 26 to give Apple Intelligence a typed view of your app's data.
The entities(matching:) method is what makes Apple Intelligence fluent in your app's nouns. When the user asks "open my grocery list note," the assistant tokenizes "grocery list," calls your query with that string, and binds the result to a Note-typed parameter on whatever intent it decides to run. The query is the bridge between natural language and your domain model. Invest in it. (I shipped an intent last quarter where I'd left suggestedEntities() returning []. Siri Suggestions never lit up, and it took me a day of staring at logs to realize I'd starved the indexer.)
Exposing intents through AppShortcutsProvider
An AppShortcutsProvider registers a fixed set of canonical phrases that work without the user opening Shortcuts.app first. These are the voice commands Siri will recognize the moment your app is installed.
struct NotesShortcuts: AppShortcutsProvider {
static var appShortcuts: [AppShortcut] {
AppShortcut(
intent: FavoriteLatestNoteIntent(),
phrases: [
"Favorite my latest \(.applicationName) note",
"Star the most recent note in \(.applicationName)"
],
shortTitle: "Favorite Latest Note",
systemImageName: "star.fill"
)
AppShortcut(
intent: CreateReminderIntent(),
phrases: [
"Add \(\.$text) to \(.applicationName)",
"Create a \(.applicationName) reminder for \(\.$text)"
],
shortTitle: "New Reminder",
systemImageName: "checklist"
)
}
}
Every phrase must contain \(.applicationName) at least once. That's the invocation token. You can have up to ten shortcuts per app, and the system will pre-index them at install time. The Action Button picker on iPhone 15 Pro and later reads from this same list, as does the Apple Watch Action button on Ultra models.
Interactive snippets in iOS 26
The headline iOS 26 feature is interactive snippets. An intent can now return a SwiftUI view that the system renders inline in Siri, Spotlight, or the Assistant panel, and the buttons inside that view can run further intents without ever opening your app. This replaces the old static snippet API and is the closest thing iOS has had to a true mini-app surface.
struct LatestNoteIntent: AppIntent {
static let title: LocalizedStringResource = "Show Latest Note"
static let openAppWhenRun = false
func perform() async throws -> some IntentResult & ShowsSnippetView {
let note = try await NoteStore.shared.latest()
return .result(view: NoteSnippet(note: note))
}
}
struct NoteSnippet: View {
let note: Note?
var body: some View {
VStack(alignment: .leading, spacing: 12) {
Text(note?.title ?? "No notes yet")
.font(.headline)
if let body = note?.body {
Text(body).lineLimit(4)
}
HStack {
Button(intent: FavoriteLatestNoteIntent()) {
Label("Favorite", systemImage: "star")
}
Button(intent: OpenNoteIntent(note: note)) {
Label("Open", systemImage: "arrow.up.right.square")
}
}
.buttonStyle(.glass)
}
.padding()
}
}
The two Button(intent:) initializers are the magic. Tapping them runs another AppIntent in-place, and if that intent also returns a snippet view, the panel updates without dismissing. The whole interaction loop happens in the system UI, in the background, often without unlocking the device. The .glass button style ships as part of the iOS 26 design language; if you want a primer on the materials, the Liquid Glass complete guide walks through the underlying view modifiers. For richer dashboards inside a snippet, the patterns from the reusable glass effect component article map almost one-to-one. Snippets, control widgets, and Live Activities all share the same restricted SwiftUI subset.
Apple Intelligence and the @AssistantIntent macro
iOS 26 added the @AssistantIntent macro, which annotates an AppIntent as something the on-device Apple Intelligence model is allowed to invoke autonomously. You opt into one of a fixed set of assistant schemas (Apple-defined contracts like .notes.createNote, .reminders.complete, .email.search, or .system.openFile) and the macro generates the boilerplate that exposes your implementation to the assistant.
@AssistantIntent(schema: .notes.createNote)
struct CreateNoteAssistantIntent: AppIntent {
@Parameter(title: "Content") var content: String
@Parameter(title: "Title") var title: String?
func perform() async throws -> some IntentResult & ReturnsValue<Note> {
let note = try await NoteStore.shared.create(
title: title ?? "Untitled", body: content
)
return .result(value: note)
}
}
The schema constraints are strict by design. The parameter types, the return type, and even the parameter names are fixed. The macro will fail to compile if your signature drifts. That's the cost of letting an opaque model call into your app: Apple needs to guarantee shape compatibility so the assistant can compose intents across apps without each app having to publish a custom grammar. If you've worked with Apple's on-device models before, the constraints will feel familiar; the Foundation Models framework guide covers the same trade-off for direct LLM calls.
You can mix and match. An intent can carry both @AssistantIntent for Apple Intelligence and a regular AppShortcut entry for Siri voice. The same code path serves both surfaces.
How to expose App Intents to Spotlight
Spotlight surfacing happens automatically once two things are true: your intent is referenced by an AppShortcutsProvider, and you've donated representative AppEntity instances via CSSearchableItem's associateAppEntity(_:) method. The index lives on-device and updates incrementally; you don't manage it directly.
Once indexed, the user can type "grocery" into Spotlight, tap the matching note, and be deep-linked into your app. Or they can tap any of your AppShortcuts phrases that appear underneath. If the intent supports it, snippet views render inline in Spotlight results too, the same way they render in Siri. Deep links from Spotlight follow the same NavigationStack patterns I covered in the SwiftUI NavigationStack guide: wire the entity's id into a NavigationPath on launch and you're done.
App Intents vs SiriKit: what changed?
SiriKit is officially deprecated as of iOS 26 for new development. Existing apps continue to work, but new domains haven't been added since iOS 16, and several legacy domains (Messaging, Lists & Notes) now route through the App Intents bridge under the hood. The practical comparison:
Aspect
SiriKit (legacy)
App Intents (iOS 26)
Language
Objective-C friendly, Swift wrappers
Pure Swift, macros, property wrappers
Definition
.intentdefinition file, codegen
Swift types, no extra files
Domains
Fixed list (Messaging, Payments, etc.)
Any domain via schemas + custom intents
Extension target
Required (Intents extension)
Optional, most intents run in-app
UI
Static snippets, limited SwiftUI
Interactive snippets with nested intents
Apple Intelligence
Not supported
First-class via @AssistantIntent
Spotlight
Separate CSSearchableItem only
Unified entity + searchable item
Shortcuts
Manual donation
Auto from AppShortcutsProvider
If you have a SiriKit codebase still shipping, my recommendation is to migrate one domain at a time, starting with whichever intent has the lowest parameter count. The two systems can coexist in the same binary; iOS 26 will prefer the App Intents implementation when both are registered for the same action.
Testing and debugging App Intents
Intents are testable like any other Swift type. Instantiate, set parameters, call perform(), assert on the result. The harder part is debugging system-driven runs, where the intent fires from Spotlight or Siri and you can't attach Xcode at launch time.
import Testing
@testable import MyApp
@Suite struct FavoriteLatestNoteIntentTests {
@Test func favoritesWhenNotesExist() async throws {
let store = MockNoteStore(notes: [.fixture(title: "Hello")])
AppDependencyManager.shared.add(dependency: store)
let intent = FavoriteLatestNoteIntent()
let result = try await intent.perform()
#expect(store.favoritedIds.contains(where: { $0 != nil }))
#expect(result.dialog?.key == "Favorited Hello.")
}
}
For system runs, the secret tool is the App Intents console pane added to Xcode 26 (View > Debug Area > App Intents Console). It streams every intent invocation, including those triggered by Siri, Spotlight, and the Assistant, with parameter values, timing, and any thrown errors. Combined with the new os_intent_log subsystem, you can finally answer "why didn't Siri pick my intent?" without guesswork. The official App Intents documentation has the full subsystem list, and the WWDC session on bringing your app to Apple Intelligence walks through the assistant schema model in detail.
Frequently Asked Questions
Do App Intents require an Intents extension target?
No. As of iOS 16 and confirmed in iOS 26, App Intents run in your main app target by default. You only need a separate extension if your intent must run in a sandboxed background context such as a Focus filter, a widget configuration intent, or when you explicitly want a smaller memory footprint for frequent system calls.
Can App Intents return a SwiftUI view?
Yes. Adopt ShowsSnippetView on your intent's result and return .result(view: MyView()). In iOS 26 these snippets are interactive, so buttons inside them can invoke other App Intents using Button(intent:) without leaving the system UI.
How do I make my app work with Apple Intelligence?
Annotate an AppIntent with the @AssistantIntent macro and pass one of the supported assistant schemas, such as .notes.createNote or .reminders.complete. The macro enforces the schema's parameter signature and registers your implementation with the on-device assistant.
What is the difference between AppShortcut and AppIntent?
An AppIntent is the action itself: the Swift type with parameters and a perform() method. An AppShortcut is a system registration that pairs an intent with voice phrases, an icon, and a short title so Siri and Spotlight can surface it without user setup. One intent can be referenced by zero, one, or several shortcuts.
Why isn't Siri recognizing my App Intent phrase?
Three common causes: the phrase doesn't include \(.applicationName), the app hasn't been launched once since install (iOS pre-indexes shortcuts at first launch), or another app registered an overlapping phrase that won the disambiguation. Use the App Intents console in Xcode 26 to see which intent the system attempted to match.
A hands-on walkthrough of the Xcode 26 SwiftUI preview crash on certain @Observable types, what the crash log really means, three workarounds that work today, and the bug report shape Apple triages fastest.
Why iOS 26's .glassEffect() modifier silently fails in real SwiftUI hierarchies, the four preconditions Apple's docs gloss over, and the exact GlassEffectContainer fix I used in production.
Apple shipped the Liquid Glass design language with iOS 26, and SwiftUI exposes most of it through a single new modifier: .glassEffect(_:in:). The API surface is tiny. The behavior is not.