An app intent is a Swift type that adopts AppIntent: it has a title, optional @Parameter properties for input, and a perform() method that does the work and returns an IntentResult. If the input is your own data, such as a habit or a document, you describe it with an AppEntity and give the system an EntityQuery so it can find instances. To make an intent easy to run by voice and show up in the Shortcuts app, you list it in an AppShortcutsProvider as an AppShortcut with spoken phrases. To generate the boilerplate for your own intents, try our App Intents and App Shortcuts builder.
Write the intent
Apple's description of the protocol: implement title and description with localized strings, add a @Parameter property for each piece of input, and put the action in perform(). Before the system calls perform(), it resolves the parameters, either by working out the value or by asking the person to supply it. AppIntent is available from iOS 16, macOS 13, watchOS 9 and tvOS 16.
The sample below is a small habit tracker. The first block is the data the intents will use; the entity, query, intent and shortcuts follow in the next sections. All blocks together make one file that type-checks with Swift 6.4 against the iOS 26 SDK.
import AppIntents
struct Habit: Identifiable, Sendable {
let id: UUID
var name: String
var count: Int
}
actor HabitStore {
static let shared = HabitStore()
private var habits = [
Habit(id: UUID(), name: "Water", count: 0),
Habit(id: UUID(), name: "Stretch", count: 0),
]
func all() -> [Habit] { habits }
func habits(for ids: [UUID]) -> [Habit] {
habits.filter { ids.contains($0.id) }
}
func increment(_ id: UUID, by amount: Int) -> Int {
guard let index = habits.firstIndex(where: { $0.id == id }) else { return 0 }
habits[index].count += amount
return habits[index].count
}
}
Describe your data with AppEntity and EntityQuery
AppEntity makes a custom type usable as an intent parameter. Apple says an entity must have a unique identifier (the system may save it inside a shortcut), that UUID, String or Int are the simplest id types, and that it is often better to make a small entity type for intents than to adopt the protocol on your main data model. Mark the properties the system should see with @Property, and give the entity a displayRepresentation and a static defaultQuery. An AppEntity instance has a total size limit of 10 MB including its child properties.
EntityQuery is how the system finds entities. entities(for:) takes identifiers and returns matching instances; Apple says to omit any entity that is no longer available. suggestedEntities() returns the initial options when the system shows a list of choices.
struct HabitEntity: AppEntity {
static let typeDisplayRepresentation: TypeDisplayRepresentation = "Habit"
static let defaultQuery = HabitQuery()
let id: UUID
@Property(title: "Name")
var name: String
var displayRepresentation: DisplayRepresentation {
DisplayRepresentation(title: "\(name)")
}
init(_ habit: Habit) {
self.id = habit.id
self.name = habit.name
}
}
struct HabitQuery: EntityQuery {
func entities(for identifiers: [UUID]) async throws -> [HabitEntity] {
await HabitStore.shared.habits(for: identifiers).map(HabitEntity.init)
}
func suggestedEntities() async throws -> [HabitEntity] {
await HabitStore.shared.all().map(HabitEntity.init)
}
}
Add parameters and return a result
Apple's parameter guide says a non-optional property is a required parameter, which the system asks for when it is missing, and an optional one is treated as optional. Supported parameter types include Bool, Int, Double, String, Date, Duration, Measurement, URL, collections of those, and your own AppEntity and AppEnum types. parameterSummary describes the action and its parameters in one sentence for the Shortcuts editor.
The perform() method returns some IntentResult. Apple suggests combining the ReturnsValue, ProvidesDialog, OpensIntent and ShowsSnippetView result types with the matching .result(...) methods.
| You return | Use it for |
|---|---|
.result() | The action finished and has nothing to say |
.result(dialog:) | Text the system can show or speak (IntentDialog) |
.result(value:) | A value the system can pass to the next action; ReturnsValue<Value> |
.result(value:dialog:) | Both, as in the sample below |
.result(opensIntent:) and variants | Ask the system to open your app and run another intent |
.result(dialog:view:) and variants | Add a SwiftUI snippet; the system may show it where there is a screen |
Apple also warns that the system might not display the IntentDialog you return, for example when someone uses Siri away from the screen, so do not depend on it as the only place a result appears.
struct LogHabitIntent: AppIntent {
static let title: LocalizedStringResource = "Log Habit"
static let description = IntentDescription("Add to today's count for a habit.")
@Parameter(title: "Habit")
var habit: HabitEntity
@Parameter(title: "Amount", default: 1)
var amount: Int
static var parameterSummary: some ParameterSummary {
Summary("Log \(\.$amount) for \(\.$habit)")
}
func perform() async throws -> some IntentResult & ReturnsValue<Int> & ProvidesDialog {
let total = await HabitStore.shared.increment(habit.id, by: amount)
return .result(value: total, dialog: "\(habit.name) is at \(total) today.")
}
}
Open the app with supportedModes
openAppWhenRun is the older switch for this. Apple's page now marks it deprecated and says to use supportedModes instead; it adds that setting openAppWhenRun to true is an error if the intent runs in an app extension. supportedModes and IntentModes are available from version 26 of iOS, iPadOS, macOS, tvOS, visionOS and watchOS, so guard the property with @available if you still support earlier systems.
Apple lists these values for supportedModes:
| Value | What it does |
|---|---|
.background | Runs the action entirely in the background |
.foreground(.immediate) | Brings the app to the foreground before the action runs |
.foreground(.dynamic) | Runs in the background and can optionally move to the foreground |
.foreground(.deferred) | Runs in the background, then moves to the foreground before the action completes |
[.background, .foreground(.deferred)] | Starts in the background and moves to the foreground before the action finishes |
You can combine them as an option set: .foreground plus .background runs in the foreground when possible but allows the background, and .background plus .foreground(.dynamic) allows either with a preference for the background. Inside perform(), systemContext tells you the current mode and whether a move to the foreground is possible, and continueInForeground(_:alwaysConfirm:) asks to move; if the move is not possible the system throws a notAllowed error. Apple's advice is to design intents to run in the background whenever possible and add foreground modes only for interaction or interface updates.
@available(iOS 26.0, *)
struct OpenHabitsIntent: AppIntent {
static let title: LocalizedStringResource = "Open Habits"
static let supportedModes: IntentModes = .foreground
func perform() async throws -> some IntentResult {
.result()
}
}
Offer App Shortcuts with AppShortcutsProvider
An App Shortcut pairs an intent with a short title, an image and spoken phrases. You declare them in a type that conforms to AppShortcutsProvider (iOS 16 and later) and list them in appShortcuts. Apple says the compiler generates what the system needs, so shortcuts are available as soon as the app is installed and you do not register them yourself.
Each phrase is an AppShortcutPhrase. Apple's Human Interface Guidelines say that phrases need to include your app name, and the way to write it is the \(.applicationName) token (AppShortcutPhraseToken.applicationName). The same page says to keep phrases brief and memorable and to offer natural variants.
struct HabitShortcuts: AppShortcutsProvider {
static var appShortcuts: [AppShortcut] {
AppShortcut(
intent: LogHabitIntent(),
phrases: [
"Log a habit in \(.applicationName)",
"Add to my habits in \(.applicationName)",
],
shortTitle: "Log Habit",
systemImageName: "checkmark.circle"
)
}
}
Apple also provides SiriTipView and SiriTipUIView to show the phrases for an intent inside your app, and ShortcutsLink to open your app's page in the Shortcuts app. Apple recommends localized strings for intents, App Shortcuts and parameters.
Where intents show up
Apple's App Intents overview lists the places the framework reaches:
- Siri, where people can use your actions and content by voice.
- Spotlight, which can lead people from search results to your data; entities that you index with Core Spotlight help here.
- The Shortcuts app, where people build workflows that include your actions, and where App Shortcuts appear.
- The Action button on iPhone and Apple Pencil, which can be set to run your actions.
- Widgets, controls and Live Activities, which use app intents for interactive buttons and toggles.
- Focus, where you can define custom Focus modes and respond to Focus changes.
Apple's guide to the framework adds that many intents can run quietly in the background, that an app extension can run some of them so the app does not have to launch, and that code for an intent that can run either way can live in a Swift package shared by the app and the extension.
Sources
All read on 1 November 2026.
- AppIntent, including supportedModes, openAppWhenRun and perform())
- Creating your first app intent
- Adding parameters to an app intent
- IntentModes
- IntentResult, ReturnsValue, ProvidesDialog and IntentDialog
- AppEntity, EntityQuery and Defining app entities for your custom data types
- App Shortcuts, AppShortcutsProvider, AppShortcut and AppShortcutPhraseToken
- App Shortcuts, Human Interface Guidelines (app name in phrases)
- App Intents and Getting started with the App Intents framework
Apple's availability and behavior change; check the pages above for current details.