7.9 — AppIntents & Shortcuts
Opening scenario
Your app has a “Start workout” button. A power user wants to say “Hey Siri, start my morning run” without opening the app. The PM wants the same action available from a Lock Screen widget, the Action Button on iPhone 15 Pro, the Shortcuts app, Spotlight search, the Apple Watch, and — as of iOS 18 — Apple Intelligence. AppIntents is the framework that unifies all of these surfaces. Write the action once; it appears everywhere.
| Context | What it usually means |
|---|---|
| Reads “AppIntent” | Has defined a basic intent |
| Reads “AppShortcutsProvider” | Has registered Siri shortcuts |
| Reads “AppEntity” | Has modeled custom domain objects |
| Reads “IntentParameter / dynamic options” | Has built parameterized intents |
| Reads “AppIntentVocabulary” | Has tuned Siri recognition |
Concept → Why → How → Code
Concept
AppIntents replaces the old INIntent / SiriKit / Intents Extension / Intents UI Extension stack with a single, Swift-native, code-only framework. Three core protocols:
AppIntent— an action your app exposes. Has parameters, performs work, returns a result.AppEntity— a domain object your app understands (Workout, Recipe, Habit). Can be passed as a parameter or returned.AppShortcutsProvider— registersAppShortcutdefinitions with Siri at install time (no user setup required).
The result: one Swift file defines a feature that’s invokable from Siri, the Shortcuts app, Lock Screen widgets (Buttons with intent:), Action Button, Spotlight, Apple Watch’s Smart Stack, the Action menu on Vision Pro, and Apple Intelligence’s tool-use surface.
Why
- Free surface coverage — write once, appear in 8+ system places.
- No extension target — runs in-process or out-of-process based on the platform; you don’t manage that.
- Type-safe — parameters are Swift enums, structs, entities. No NSDictionary plumbing.
- Apple Intelligence integration — iOS 18+ uses your AppIntent metadata as tool definitions for the on-device LLM.
How — a basic AppIntent
import AppIntents
struct StartWorkoutIntent: AppIntent {
static var title: LocalizedStringResource = "Start Workout"
static var description = IntentDescription("Begin a workout in MyFitnessApp.")
@Parameter(title: "Activity")
var activity: ActivityType
static var parameterSummary: some ParameterSummary {
Summary("Start a \(\.$activity) workout")
}
@MainActor
func perform() async throws -> some IntentResult & ProvidesDialog {
try await WorkoutService.shared.start(activity: activity)
return .result(dialog: "Started your \(activity.localizedName).")
}
}
enum ActivityType: String, AppEnum {
case running, cycling, yoga, strength
static var typeDisplayRepresentation: TypeDisplayRepresentation = "Activity"
static var caseDisplayRepresentations: [ActivityType: DisplayRepresentation] = [
.running: "Run",
.cycling: "Cycle",
.yoga: "Yoga",
.strength: "Strength"
]
var localizedName: String {
Self.caseDisplayRepresentations[self]?.title.key ?? rawValue
}
}
That single struct now appears in:
- The Shortcuts app under your app.
- Siri (after user has used it once, or always with an
AppShortcutsProvider— see below). - Lock Screen widgets via
Button(intent: StartWorkoutIntent(activity: .running)) { ... }.
AppShortcutsProvider — zero-setup Siri
struct MyAppShortcuts: AppShortcutsProvider {
static var appShortcuts: [AppShortcut] {
AppShortcut(
intent: StartWorkoutIntent(activity: .running),
phrases: [
"Start a run in \(.applicationName)",
"Begin running with \(.applicationName)",
"\(.applicationName) start run"
],
shortTitle: "Start Run",
systemImageName: "figure.run"
)
AppShortcut(
intent: ViewTodayHabitsIntent(),
phrases: ["Show my habits in \(.applicationName)"],
shortTitle: "Today's Habits",
systemImageName: "checklist"
)
}
}
AppShortcutsProvider is auto-registered at install time — the user never has to “enable” the shortcut. They just say “Start a run in MyFitnessApp” and it works. Limit: 10 AppShortcuts per app.
AppEntity — exposing domain objects
struct HabitEntity: AppEntity {
let id: UUID
var name: String
static var typeDisplayRepresentation: TypeDisplayRepresentation = "Habit"
var displayRepresentation: DisplayRepresentation { DisplayRepresentation(title: "\(name)") }
static var defaultQuery = HabitQuery()
}
struct HabitQuery: EntityQuery {
func entities(for identifiers: [HabitEntity.ID]) async throws -> [HabitEntity] {
await HabitStore.shared.habits.filter { identifiers.contains($0.id) }
}
func suggestedEntities() async throws -> [HabitEntity] {
await HabitStore.shared.habits
}
}
Now an intent can take a HabitEntity parameter and Siri/Shortcuts will offer a list picker:
struct CompleteHabitIntent: AppIntent {
static var title: LocalizedStringResource = "Complete Habit"
@Parameter(title: "Habit") var habit: HabitEntity
func perform() async throws -> some IntentResult {
await HabitStore.shared.complete(id: habit.id)
return .result()
}
}
Dynamic options
@Parameter(
title: "Workout",
optionsProvider: WorkoutOptionsProvider()
)
var workout: String
struct WorkoutOptionsProvider: DynamicOptionsProvider {
func results() async throws -> [String] {
try await WorkoutCatalog.shared.availableNames()
}
}
Returning results — ReturnsValue, OpensIntent, ProvidesDialog, ShowsSnippetView
func perform() async throws -> some IntentResult & ReturnsValue<Int> & ProvidesDialog {
let count = await HabitStore.shared.todayCount
return .result(value: count, dialog: "You've completed \(count) habits today.")
}
For Spotlight/widget snippets:
func perform() async throws -> some IntentResult & ShowsSnippetView {
.result(view: HabitSummaryView())
}
Where intents run
- In-process when the host UI needs to update (Lock Screen Button tap, widget Button tap).
- Out-of-process (background) for Siri/Shortcuts execution. You don’t pick — set
static var openAppWhenRun = trueif your intent absolutely requires app foreground. ForegroundContinuableIntent— for intents that might need to open the app: start in background, escalate if needed.
Spotlight donation
CSSearchableItem integration with AppEntity is automatic in iOS 17+: registered AppEntities appear in Spotlight, and tapping one runs your default intent. No NSUserActivity boilerplate.
iOS 18 — Apple Intelligence tool use
When the user invokes Apple Intelligence and asks “What habits did I complete today?”, the system can use your CompleteHabitIntent and a GetHabitsTodayIntent as tools, parameterized by the conversation context. Your AppIntents become LLM-callable functions. No additional code beyond well-named intents with clear description strings.
This is why naming and descriptions matter enormously now. description: "Returns the user's habits completed today, with completion timestamps" is better than description: "Habits intent".
In the wild
- Shortcuts (built-in) — the canonical AppIntents host.
- Bear, Things 3, Drafts — extensive intent coverage for “Create note,” “Add task,” “Open inbox.”
- Carrot Weather — donated intents for “Show forecast for [location]” feeding Siri and Lock Screen.
- Toolbox for HomeKit, Home+ 6 —
AppEntity-based intents for “Turn on [room].” - Apple’s own apps (Reminders, Calendar, Notes, Mail) — all migrated from
INIntenttoAppIntent.
Common misconceptions
- “AppShortcuts need user setup.” They don’t. Define them in
AppShortcutsProviderand Siri recognizes the phrases at install time. - “AppIntents are only for Siri.” They power Shortcuts, widgets, Action Button, Spotlight, Apple Intelligence, Smart Stack, and more. Siri is one of many surfaces.
- “I can put any complex UI in an intent result.” Snippet views must be lightweight SwiftUI — no heavy interaction, no scroll views with full app state. Treat them as widgets.
- “AppEntity is just a struct that conforms to a protocol.” It also needs an
EntityQueryfor ID-based lookup and ideallysuggestedEntitiesfor picker UX. Without those, the entity isn’t useful in dynamic intents. - “AppIntents replace
NSUserActivityfor handoff.” They do for the action surface, butNSUserActivitystill exists for Handoff between devices, Continuity, and SiriKit donations on older OSes.
Seasoned engineer’s take
AppIntents is the highest-leverage code in your app. Three intents, well-named with clear descriptions, can light up your app on every Apple surface: home screen widget, Lock Screen, Watch face, Spotlight, Action Button, Apple Intelligence. The cost is one Swift file per intent. The competition is people who haven’t shipped intents at all and whose features only exist when the app is open.
Two practices that pay off:
- Make your
descriptionstrings narrative. They are read by the on-device LLM as tool descriptions. “Returns the user’s three most recently completed habits with their completion times” is parsed and matched far better than “Get habits.” - Avoid optional parameters when you can. Siri’s dialog UX for “What workout?” is much smoother than “Workout? (optional) Activity? (optional).” Provide good defaults and require only what’s necessary.
For migrations: if you’re on legacy INIntent extensions, the AppIntents migration is one of the highest-ROI refactors available. You delete entire extension targets, lose ~1000 lines of plist+Swift+ObjC plumbing, and gain Apple Intelligence support for free.
TIP: Use
IntentDonationManagerto donate intents the user has performed even if they weren’t invoked via Siri. The OS uses these to rank your AppShortcuts in Spotlight and the Suggestions widget.
WARNING: Don’t let an AppIntent perform a destructive action without confirmation. Provide
parameterSummarywith clear language, and for “Delete X” intents, setopenAppWhenRun = true(or useConfirmationflow) so the user sees the consequence.
Interview corner
Junior: “How do you add a ‘Start workout’ Siri shortcut to your app?”
Define an
AppIntentwith aperform()method. Register it in anAppShortcutsProviderwith one or more invocation phrases. Build and run; Siri recognizes the phrase at install time, no user setup needed.
Mid: “How would you let the user say ‘Complete my habit’ and have Siri ask which one?”
Define a
HabitEntity(withEntityQuerythat returns the user’s habits). DefineCompleteHabitIntentwith@Parameter var habit: HabitEntity. Siri sees the entity has a query, runssuggestedEntities(), and presents a picker dialog. Once selected,perform()runs against the chosen habit’s ID. Donate completed intents viaIntentDonationManagerso frequent habits appear higher in the picker.
Senior: “Design the AppIntent layer for a recipe app that exposes search, view, save, and cook actions to Siri, widgets, Lock Screen, and Apple Intelligence.”
Four
AppIntents:SearchRecipesIntent(query: String) -> RecipeEntity[],ShowRecipeIntent(recipe: RecipeEntity)withopenAppWhenRun = true,SaveRecipeIntent(recipe: RecipeEntity),StartCookingIntent(recipe: RecipeEntity)(background, posts aUNNotificationwith a “Next step” action). OneRecipeEntitywith anEntityQuerybacked by SwiftData. Register the top three inAppShortcutsProviderwith natural phrases (“Search recipes for (parameter), Show me (parameter), Start cooking (parameter)”). Richdescriptionstrings on every intent so Apple Intelligence can invoke them as tools: “Searches the user’s saved recipes by ingredient or dish name and returns up to five matches.” Lock Screen widget usesButton(intent: SearchRecipesIntent(query: "dinner")) { ... }. Spotlight surfaces individualRecipeEntityinstances automatically; tapping one runsShowRecipeIntentas the default. Test on iOS 18 device with Apple Intelligence enabled to verify tool invocation in chat.
Red flag: “We still ship an Intents Extension and an Intents UI Extension for our SiriKit shortcuts.”
In 2026 this is legacy code unless you specifically need pre-iOS 16 support. Migrating to AppIntents deletes two extension targets, removes the Intents framework code, and gives you the full modern surface (widgets, Action Button, Apple Intelligence) for free.
Lab preview
Lab 7.2 — Widget extension builds a parameterized AppIntent and surfaces it via an interactive widget button — the cleanest possible demonstration of “write the intent once, run it from a widget tap.”