# App Intents and App Shortcuts in Swift > How to write an AppIntent with parameters, add an AppEntity and EntityQuery, return a dialog or a value, choose supportedModes, and ship App Shortcuts phrases. - Canonical page: https://obelisk.club/blog/app-intents-shortcuts-guide - Author: Peter Zhang - Published: 2026-11-01 - Tags: app-intents, shortcuts, siri, swift, ios, indie 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](/tools/app-intents-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. ```swift 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. ```swift 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` | | `.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. ```swift 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 & 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. ```swift @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. ```swift 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](https://developer.apple.com/documentation/appintents/appintent), including [supportedModes](https://developer.apple.com/documentation/appintents/appintent/supportedmodes), [openAppWhenRun](https://developer.apple.com/documentation/appintents/appintent/openappwhenrun) and [perform()](https://developer.apple.com/documentation/appintents/appintent/perform()) - [Creating your first app intent](https://developer.apple.com/documentation/appintents/creating-your-first-app-intent) - [Adding parameters to an app intent](https://developer.apple.com/documentation/appintents/adding-parameters-to-an-app-intent) - [IntentModes](https://developer.apple.com/documentation/appintents/intentmodes) - [IntentResult](https://developer.apple.com/documentation/appintents/intentresult), [ReturnsValue](https://developer.apple.com/documentation/appintents/returnsvalue), [ProvidesDialog](https://developer.apple.com/documentation/appintents/providesdialog) and [IntentDialog](https://developer.apple.com/documentation/appintents/intentdialog) - [AppEntity](https://developer.apple.com/documentation/appintents/appentity), [EntityQuery](https://developer.apple.com/documentation/appintents/entityquery) and [Defining app entities for your custom data types](https://developer.apple.com/documentation/appintents/defining-app-entities-for-your-custom-data-types) - [App Shortcuts](https://developer.apple.com/documentation/appintents/app-shortcuts), [AppShortcutsProvider](https://developer.apple.com/documentation/appintents/appshortcutsprovider), [AppShortcut](https://developer.apple.com/documentation/appintents/appshortcut) and [AppShortcutPhraseToken](https://developer.apple.com/documentation/appintents/appshortcutphrasetoken) - [App Shortcuts](https://developer.apple.com/design/human-interface-guidelines/app-shortcuts), Human Interface Guidelines (app name in phrases) - [App Intents](https://developer.apple.com/documentation/appintents) and [Getting started with the App Intents framework](https://developer.apple.com/documentation/appintents/getting-started-with-the-app-intents-framework) *Apple's availability and behavior change; check the pages above for current details.*