App Shortcuts: Give Superpowers to Your App in a Matter of Minutes
Useful Links
Table Of Contents
Introduction
1. Creating Your First App Intent (No Parameters)
2. Registering the App Intent as an AppShortcut
3. Adding an AppIntent with a Parameter
3.1 Defining the AppEntity
3.2 Creating the App Intent with a Parameter
3.3 Adding a Shortcut with a Parameter
3.4 Updating App Shortcut Parameters
🎉 Recap
🌱 What’s Next?
Introduction
Want your app to play nicely with Siri, Shortcuts, widgets, and more—without a ton of work? In this post, I’ll show you how App Intents can unlock a world of superpowers for your app in just a few minutes.
We’ll start by teaching Siri how to perform a simple action:
- Define an
AppIntentthat doesn’t require user input - Trigger it using an
AppShortcut, so users can say something like “Plant a seed in my Garden”
Then, we’ll grow things a bit by:
- Adding an App Intent with a parameter
- Introducing an
AppEntityso users can select specific data in our app
To keep things simple and fun, we’ll build around a small SwiftUI garden app:
- You can plant seeds
- Wait for them to grow into fruits
- Then pick the fruit, which generates more seeds—just like a real garden.

Let’s get started!
1. Creating Your First App Intent (No Parameters)
We’ll start with the basics.
The first intent we’ll add is PlantSeedIntent. This one doesn’t need any parameters—it simply plants a seed in the garden when triggered by Siri, Spotlight, or the Shortcuts app.
🌱 The Code
// 1. Import AppIntents Framework.
import AppIntents
// 2. Define a new AppIntent.
struct PlantSeedIntent: AppIntent {
// 3. Set a title shown to the user.
static var title: LocalizedStringResource = "Plant a Seed"
// 4. Perform the seed-planting action and return an IntentResult.
func perform() async throws -> some IntentResult & ProvidesDialog {
// 5. Check if there's at least one seed available to plant.
guard let seed = await Model.shared.bank.seeds.first else {
// 6. If there are no seeds, return a dialog message to the user.
return .result(dialog: IntentDialog("No seeds to plant."))
}
// 7. Plant the first available seed.
await Model.shared.plant(seed: seed)
// 8. Return a dialog confirming the planting action.
return .result(dialog: IntentDialog("\(seed.fruitName) seed was planted for you."))
}
}
🧱 Step-by-Step
- Import AppIntents
This framework gives you everything you need to define and expose your app’s actions to the system. - Create a new struct that conforms to
AppIntent
This struct tells the system what your intent is called, what it does, and what happens when it runs. - Give your intent a title
This title is displayed in Siri, Spotlight, and the Shortcuts app to describe the action. - Implement the
perform()method
This is where the logic for your intent lives. It returns a result that lets Siri (or the Shortcuts app) know what happened — including any message you want to show or speak. - Check for available seeds
Before planting, the app checks whether there’s at least one seed in the seed bank. - Handle the empty state
If there are no seeds, the method returns a dialog explaining that to the user. - Plant the seed
If a seed is found, it is passed to theModel.plant. - Return a confirmation dialog
Finally, the method returns a message confirming the action was completed—Siri can speak this aloud!
2. Registering the App Intent as an AppShortcut
Now that we’ve defined PlantSeedIntent, we need to make it discoverable.
This means registering it with the system so it appears in:
- Siri Suggestions
- Spotlight search
- The Shortcuts app
To do this, we define an AppShortcutsProvider.
🌱 The Code
// 1. Import AppIntents.
import AppIntents
// 2. Define a provider for app-wide shortcuts conforming to the AppShortcutsProvider protocol.
struct GardenAppShorcutsProvider: AppShortcutsProvider {
// 3. Define static `appShortcuts` property.
public static var appShortcuts: [AppShortcut] {
AppShortcut(
// 4. The AppIntent that will be triggered.
intent: PlantSeedIntent(),
// 5. Phrases users can speak to invoke this shortcut.
phrases: [
"Plant a seed in my \(.applicationName)"
],
// 6. A short, user-friendly label for the shortcut.
shortTitle: "Plant a Seed",
// 7. The SF Symbol used as the icon for this shortcut.
systemImageName: "apple.meditate"
)
}
}
🧱 Step-by-Step
- Import AppIntents
This gives you access to the types and protocols needed to define and register App Shortcuts, includingAppShortcutsProviderandAppShortcut. - Define a Shortcut Provider
Create a new struct that conforms toAppShortcutsProvider. This struct will act as the registry for all the shortcuts your app offers. - Expose your shortcuts via
appShortcuts
InsideappShortcuts, you return the list of available shortcuts — even just one works, thanks to the special result builder syntax. - Specify the intent to trigger
Use your previously definedPlantSeedIntentas the intent that this shortcut should run when activated. - Add voice trigger phrases
Provide one or more phrases max (~1000 phrases in total) users can say to run this shortcut. You must use the dynamic placeholder\(.applicationName)to help Siri know what app to launch. - Set a short title
This title appears in system UI like Spotlight and the Shortcuts app. Keep it clear and actionable. - Set a system image name
Choose an SF Symbol to visually represent your shortcut when it’s displayed in Spotlight or the Shortcuts app. - 🎉 Well done!
You’ve just taught Siri how to plant seeds for us in our garden app — and all it took was a few lines of code and 2 files.

❓ Quick Quiz
If appShortcuts is supposed to return an array of AppShortcut, how come we’re returning just one and it compiles? Let me know in the comments.
3. Adding an AppIntent with a Parameter
Now let’s level up. 🧙♂️
In this next section, we’ll explore a slightly more advanced — but totally approachable — skill: adding parameters to your app intents.
Why? Because now we want to say things like:
“Hey Siri, pick a cherry from my Garden.”
In other words, we’re going to let the user choose which fruit to pick.
We’ll build an AppIntent called PickFruitIntent, which takes a seed as a parameter and picks it only if it’s ripe.
Then, we’ll:
- Define an
AppEntitythat represents a seed in our garden calledSeedAppEntity - Define a new App Intent with a
SeedAppEntityparameter - Register a shortcut so Siri knows how to use it
Let’s get started! 🪄
✅ 3.1 Defining the AppEntity
In order for your intent to accept a seed as a parameter, you need to define a type the system can understand and display. That’s where AppEntity comes in.
We’ll define a SeedAppEntity that represents a single seed in the garden — including how it should be displayed, how to match it when searched, and how to suggest ripe fruits to the user.
This allows the system (and Siri!) to offer relevant, real-time options when the user configures or invokes your shortcut.
You’ll be able to:
Think of an AppEntity like a way to expose parts of your app’s data — like individual fruits — so users can interact with them using Siri or Shortcuts.
- Let users choose a fruit from a list
- Filter only ripe fruits for suggestions
- Support custom parameters in Shortcuts app
🌱 The Code
// 1. Import the AppIntents framework.
import AppIntents
// 2. Define a struct conforming to AppEntity protocol.
struct SeedAppEntity: AppEntity {
// 3. Unique identifier required by `Identifiable`.
let id: Seed.ID
// 4. The name of the fruit shown to the user in the UI.
let title: String
// 5. Define how this fruit entity appears in the Shortcuts interface.
var displayRepresentation: DisplayRepresentation {
.init(
title: .init(stringLiteral: title),
subtitle: "",
image: DisplayRepresentation.Image(systemName: "tree.fill", isTemplate: true),
synonyms: [
.init(stringLiteral: title)
]
)
}
// 6. Initialize the AppEntity from your domain model `Seed`.
init(seed: Seed) {
id = seed.id
title = seed.fruitName
}
// 7. Display name for the entity type shown in the Shortcuts UI.
static var typeDisplayRepresentation: TypeDisplayRepresentation = .init(name: "Fruit")
// 8. Provide a default query used to resolve or suggest fruits.
static var defaultQuery = SeedQuery()
}
extension SeedAppEntity {
// 9. Define a query type to resolve and suggest SeedAppEntity values.
struct SeedQuery: EntityStringQuery {
// 10. Return seeds that match given IDs.
func entities(for identifiers: [SeedAppEntity.ID]) async throws -> [SeedAppEntity] {
// 10.1. Filter seeds by checking which IDs match.
await Model.shared.field.seeds
.filter({ identifiers.contains($0.id) })
.map(SeedAppEntity.init)
}
// 11. Return seeds matching a search string as a sectioned UI list.
public func entities(matching string: String) async throws -> IntentItemCollection<SeedAppEntity> {
// 11.1. Find seeds whose fruit name matches the input string.
let matches = await Model.shared.field.seeds
.filter({ $0.fruitName.localizedCaseInsensitiveContains(string) })
.map(SeedAppEntity.init)
// 11.2. Wrap the matches into a section for display.
return ItemCollection {
ItemSection<SeedAppEntity>("Found Ripe Fruits", entities: matches)
}
}
// 12. Return a plain list of matching seeds, no UI section.
public func entities(matching string: String) async throws -> [SeedAppEntity] {
// 12.1. Simple filtered array of fruits matching the input string.
await Model.shared.field.seeds
.filter({ $0.fruitName.localizedCaseInsensitiveContains(string) })
.map(SeedAppEntity.init)
}
// 13. Return suggested seeds as a UI section for default display.
public func suggestedEntities() async throws -> IntentItemCollection<SeedAppEntity> {
// 13.1. Filter to include only ripe fruits for suggestions.
let ripeFruits = await Model.shared.field.seeds
.filter(\.isFruitReady)
.map(SeedAppEntity.init)
// 13.2. Wrap suggestions in a section titled "Ripe Fruits".
return ItemCollection {
ItemSection<SeedAppEntity>("Ripe Fruits", entities: ripeFruits)
}
}
}
}
🧱 Step-by-Step
- Import the AppIntents framework to enable AppEntity support
Required to define and expose entities that can be used in App Shortcuts. - Define a struct conforming to
AppEntityprotocolAppEntityallows the system to recognize and present your type (like a fruit), whileHashableandEquatableare useful for Swift comparisons and collections. - Unique identifier required by
AppEntityandIdentifiable
Each entity must have an ID to distinguish it — we use aUUID. - The name of the fruit shown to the user in the UI
Atitlethat users will see when selecting a fruit in Shortcuts. - Define how this fruit entity appears in the Shortcuts interface
ThedisplayRepresentationsets how the entity looks: title, optional subtitle, icon, and synonyms. - Initialize the
AppEntityfrom your domain modelSeed
Converts your app’s internal model into the entity format used in Shortcuts. - Display name for the entity type shown in the Shortcuts UI
This is the plural or category name Siri uses, like “Fruit”. - Provide a default query used to resolve or suggest fruits
This tells the system how to search, match, and suggest fruit entities. - Define a query type to resolve and suggest
SeedAppEntityvalues
TheSeedQueryconforms toEntityStringQuery, allowing users to search and receive fruit suggestions. - Return fruits that match given IDs
This method resolves stored shortcut references (by ID) into actual entities.
10.1. Filter the model’s seeds by checking which IDs match
It looks through your app’s model and matches seeds by their ID.
- Return fruits matching a search string as a sectioned UI list
Used when the user starts typing in Shortcuts — results appear grouped in a UI section.
11.1. Find seeds whose fruit name matches the input string
Performs a case-insensitive search in your model.
11.2. Wrap the matches into a section for display
Uses ItemSection to present results nicely inside Shortcuts.
- Return a plain list of matching fruits, no UI section
Same matching logic but without UI grouping — used in different contexts.
12.1. Simple filtered array of fruits matching the input string
Just returns a filtered array based on a search match.
- Return suggested fruits as a UI section for default display
Shows suggested values even before the user starts typing.
13.1. Filter to include only ripe fruits for suggestions
Only ready-to-pick fruits will appear as shortcut suggestions.
13.2. Wrap suggestions in a section titled “Ripe Fruits”
Displays the suggestions in a user-friendly section in the a list.
✅ 3.2 Creating the App Intent with a Parameter
It’s time to build our next magical shortcut:
Pick a Fruit — but this time, you get to choose the fruit.
To make that happen, we’ll define an AppIntent that accepts a parameter of type SeedAppEntity — a special wrapper we’ll build in the next step.
The system will use that type to show you a list of fruits to pick from — and you’ll be able to say things like:
“Pick an apple from my garden”
“Pick a pear from my garden”
🌱 The Code
Here’s how your intent might look (just copy from your project):
import AppIntents
struct PickFruitIntent: AppIntent {
static var title: LocalizedStringResource = "Pick a Fruit"
// 1. Describe what this intent does so users can understand its purpose.
static var description = IntentDescription("Pick a ripe fruit of your choice.")
// 2. Show how the parameter should appear in the Shortcuts app.
static var parameterSummary: some ParameterSummary {
Summary("Pick \(\.$fruitSeed) Fruit")
}
// 3. Ensure the app opens when this shortcut is triggered.
static var openAppWhenRun: Bool = true
// 4. Declare the parameter that lets users pick a fruit.
@Parameter(title: "Fruit Seed")
// 5. Store the selected fruit seed from the user.
var fruitSeed: SeedAppEntity
// 6. Required initializer for AppIntent.
init() {}
func perform() async throws -> some IntentResult & ProvidesDialog {
// 7. Try to find the matching fruit and check if it's ripe.
guard let seed = await Model.shared.field.seeds.first(where: { $0.id == fruitSeed.id }), seed.isFruitReady else {
// 8. If the fruit isn't ripe or was already picked, return a message.
return .result(dialog: IntentDialog("The \(fruitSeed.title) is not ripe yet or has been already picked."))
}
// 9. Pick the fruit by calling the model.
await Model.shared.pick(seed: seed)
// 10. Return a confirmation message.
return .result(dialog: IntentDialog("The \(seed.fruitName) was picked for you."))
}
}
🧱 Step-by-Step
- Describe what this intent does so users can understand its purpose
IntentDescriptionprovides a sentence that helps users understand what the shortcut does when configuring it in the Shortcuts app or via Siri. - Show how the parameter should appear in the Shortcuts app
parameterSummarylets you customize the phrase shown in the Shortcuts UI. TheSummary("Pick \(\.$fruitSeed) Fruit")will render something like “Pick an Apple Fruit” when the user selects a fruit. - Ensure the app opens when this shortcut is triggered
SettingopenAppWhenRun = trueensures that your app launches when the shortcut runs, allowing users to see the action reflected live in the UI. - Declare the parameter that lets users pick a fruit
The@Parameterattribute registers a user-selectable input — here, it’s the fruit seed entity they want to pick. - Store the selected fruit seed from the user
ThefruitSeedvariable holds the actual value chosen by the user when the shortcut is executed. - Required initializer for AppIntent
A simpleinit()is needed so the system can instantiate the intent when running it or displaying it in the UI. - Try to find the matching fruit and check if it’s ripe
This line looks up theSeedmodel instance by ID and checks if the fruit is ready to be picked. - If the fruit isn’t ripe or was already picked, return a message
If the check fails, a dialog is returned explaining that the action couldn’t be completed. - Pick the fruit by calling the model
If the fruit is valid and ripe, the app logic triggers the picking action. - Return a confirmation message
The shortcut ends by confirming the action with a message, either spoken by Siri or shown to the user.
✅ 3.3 Adding a AppShortcut with a Parameter
Now that we’ve defined the PickFruitIntent and the SeedAppEntity, it’s time to expose this new action to Siri and the Shortcuts app.
Just like we did with PlantSeedIntent, we’ll use AppShortcutsProvider again — but now we’ll add a dynamic parameter. This lets the user pick a fruit right inside the Shortcuts app or using voice like: “Pick an apple from my Garden.”
This makes your shortcut feel much more personal and powerful.
🌳 The Code
struct GardenAppShortcutsProvider: AppShortcutsProvider {
public static var appShortcuts: [AppShortcut] {
...
AppShortcut(
intent: PickFruitIntent(),
phrases: [
// 1. Define a voice phrase that uses the dynamic `fruitSeed` parameter.
"Pick \(\.$fruitSeed) from my \(.applicationName)",
],
shortTitle: "Pick a Fruit",
systemImageName: "basket"
)
🧱 Step-by-Step
- Define a voice phrase that uses the dynamic
fruitSeedparameter
The phrase"Pick \(\.$fruitSeed) from my \(.applicationName)"allows users to say things like “Pick an avocado from my Garden.”
The\(\.$fruitSeed)placeholder dynamically prompts the user to select a fruit when creating the shortcut.

3.4 Updating App Shortcut Parameters
Shortcuts that rely on dynamic data — like the list of ripe fruits — need to stay up to date as your app evolves. Luckily, it’s super simple to keep Siri in sync.
In our garden app, we monitor changes to the seed list. When something changes (like planting a new seed or picking a fruit), we tell the system to update the shortcut’s options — so Siri and Shortcuts always reflect the latest fruits.
🌱 The Code
// 1. Observe changes to your list of seeds.
.onReceive(model.$field.map(\.seeds).removeDuplicates()) { _ in
// 2. Refresh shortcut parameters to reflect the latest fruit data.
GardenAppShortcutsProvider.updateAppShortcutParameters()
}
🧱 Step-by-Step
- Observe changes to your seed data
Use.onReceiveto listen for updates from the model. We specifically track changes to the list of seeds using Combine. - Call
updateAppShortcutParameters()
This is the magic call. It tells the system to refresh all AppShortcuts with parameters. Thanks to this tiny bit of code, your App Shortcuts will always reflect the freshest produce in your garden.



🎉 Recap
Brilliant! You’ve just given your app a literal voice. Let’s recap the journey:
- 🌱 Built our first AppIntent
PlantSeedIntentkicked things off — a simple, no-parameter intent that plants a seed when triggered by Siri or Shortcuts. - 🗣️ Made it speakable with a custom shortcut
UsingAppShortcutsProvider, we registered the intent and gave it a natural voice command and a friendly icon. - 🍒 Added an AppIntent with a parameter
PickFruitIntenttook things up a notch by letting the user choose which fruit to pick — only if it’s ripe! - 📦 Created an AppEntity to expose data from our app to the system
SeedAppEntityallowed us to present fruits to the user, support search, suggestions, and selection right inside the Shortcuts UI. - 🧩 Connected it all with a parameterized shortcut
We tied it together by adding a dynamic voice phrase like “Pick a cherry from my Garden,” fully powered by the entity behind the scenes.
🌱 What’s Next?
App Intents go far beyond just Siri and Shortcuts:
- 🔘 Widgets — Use your AppIntent directly as the action behind a widget button.
- 🧩 Control Center Widgets — Add quick access tiles that trigger your intent.
- 📺 Live Activities — Let users interact with your app in real-time from the Lock Screen.
- 📱 App Clips — Yup, even lightweight app clips can use App Intents!
- 💻 Cross-Platform Support — App Intents work across iOS, iPadOS, macOS, and watchOS.

The beauty of it? You don’t have to rewrite your logic for every platform. Define your AppIntent once, and you’re set — as long as that feature is supported on the target system.
Of course, not every intent will be available everywhere. Some widgets or device types may not support certain functionality — but Swift will guide you gently with compiler errors or availability checks.
So go ahead — add new superpowers to your app!
But remember what Uncle Ben said:
“With great power comes great responsibility.”
After you’ve given your app any awesome powers using App Intents, I’d love to hear about them in the comments.
And if you hit any bumps along the way or have questions, don’t hesitate to reach out — I’ll be happy to help if I can.
Thanks for reading!
If you’re hungry for more, explore WWDC sessions listed below (highly recommended):
- Meet App Intents (WWDC22)
- Spotlight your app with App Shortcuts (WWDC23)
- Explore enhancements to App Intents (WWDC23)
- Dive into App Intents (WWDC23)
- Integrate App Shortcuts Deeply (WWDC23)
- What’s new in App Intents (WWDC24)
- Design App Intents for system experiences (WWDC24)
- Bring your app’s core features to users with App Intents (WWDC24)
- Get to know App Intents (WWDC25)
