Highlight
The App Intents framework adds capabilities such as ValueRepresentation, RelevantEntities, EntityCollection, SyncableEntity, @UnionValue, LongRunningIntent, CancellableIntent, and ExecutionTargets, making cross-app data sharing smoother, large entity handling more efficient, cross-device conversations more reliable, and long-running tasks easier to control.
Core Content
Starting from a real problem
You have a travel-tracking app, and users want to share the landmarks they have saved.
A user creates a Shortcuts automation: find nearby landmarks and send them to a friend with a message. Your landmark entity already implements Transferable, so Shortcuts can convert it into a format Mail can use. That part works.
Then the user changes the scenario and wants to send the landmark to a Maps app for navigation. Here is the problem: Maps needs structured data such as coordinates and addresses, not a file. Existing FileRepresentation and DataRepresentation types handle PDFs, images, and other types with clear file formats, but they cannot represent pure structured data such as coordinates.
This is what ValueRepresentation solves. It lets your entity carry system-known structured types so data can flow between apps.
A new relevance mechanism
Another scenario: you build a music app and carefully curate a high-BPM playlist for running.
When users set up a running workout in a fitness app, the system recommends playlists. How does your new playlist enter that list?
The traditional approaches are Spotlight indexing, which lets users search for content, and interaction donation, which tells the system how users use your app. But a new playlist has a problem: the user does not know it exists, has not searched for it, and has not played it, so there is no interaction to donate.
What you need is a way to tell the system, “This playlist is relevant in a running context.” That is RelevantEntities.
Solving a performance bottleneck
As app data grows, users might tag thousands of photos at once.
Your intent definition is simple: receive an array of photo entities and add a keyword to each one. But execution is slow, because before running the intent, the system resolves every entity and fetches every property. You only need IDs, but the system resolves full photo metadata.
EntityCollection fixes this by passing only entity IDs instead of resolving full entities.
Cross-device entity references
The user asks Siri on iPhone to add a photo to an album, then moves to another device and asks Siri to tag that same photo. Siri cannot find it.
The reason is that each device generates different local IDs. To continue conversations across devices, Siri needs a stable ID that is identical everywhere, such as a server UUID or CloudKit record ID.
SyncableEntity tells the system that an entity ID is stable across devices.
Other enhancements
Parameter types now support native types such as Duration and PersonNameComponents. A single parameter can also accept multiple types with @UnionValue. Intents can run longer than 30 seconds with LongRunningIntent, handle cancellation gracefully with CancellableIntent, and specify their execution process with ExecutionTargets.
Details
ValueRepresentation: Share structured data
([0:42](https://developer.apple.com/videos/play/wwdc2026/345/?time=42))
ValueRepresentation lets your entity carry system-understood structured types, such as PlaceDescriptor from the GeoToolbox framework.
struct LandmarkEntity: AppEntity, Transferable {
var id: Int
var landmark: Landmark // Contains CLLocationCoordinate2D
static var transferRepresentation: some TransferRepresentation {
ValueRepresentation(
exporting: { entity in
PlaceDescriptor(
representations: [.coordinate(entity.landmark.locationCoordinate)],
commonName: entity.landmark.name
)
}
)
}
}
Key points:
ValueRepresentationis a new kind ofTransferRepresentation- The
exportingclosure returns a system-known type,PlaceDescriptorin this example PlaceDescriptorcontains coordinates and a name, so Maps can navigate directly
If the entity already has a PlaceDescriptor property, you can use a simpler key-path form:
struct LandmarkEntity: AppEntity, Transferable {
var id: Int
@Property var placeDescriptor: PlaceDescriptor
static var transferRepresentation: some TransferRepresentation {
ValueRepresentation(exporting: \.placeDescriptor)
}
}
RelevantEntities: Register relevant entities
([5:06](https://developer.apple.com/videos/play/wwdc2026/345/?time=306))
The RelevantEntities API lets you tell the system which entities are relevant in which contexts:
// Recommend playlists for running workouts
let playlistEntities = [dailyRun, runningMix]
let workoutContext = AppEntityContext.audio(.workout(activityType: .running))
try await RelevantEntities.shared.updateEntities(
playlistEntities, for: workoutContext
)
// Clear all entities for a specific context
try await RelevantEntities.shared.removeAllEntities(for: workoutContext)
// Remove specific entities from a specific context
try await RelevantEntities.shared.removeEntities(playlistEntities, from: workoutContext)
// Remove all entities from all contexts
try await RelevantEntities.shared.removeAllEntities()
Key points:
AppEntityContext.audio(.workout(activityType:))defines an audio-related fitness contextupdateEntities(_:for:)registers entities so the system can recommend them in the right situations- Entities remain registered until you actively remove them
removeAllEntities()can clear by context or globally
Choosing among three content discovery approaches:
- Spotlight indexing: Make content searchable and retrievable through Siri
- Interaction donation: Teach Siri how users use your app so it can recognize patterns and suggest repeated actions
- RelevantEntities: Tell the system which content is relevant in a specific context
EntityCollection: Efficiently handle large entity sets
([7:12](https://developer.apple.com/videos/play/wwdc2026/345/?time=432))
Change the parameter type from an array to EntityCollection, and the system passes only IDs instead of full entities:
struct TagPhotosIntent: AppIntent {
static let title: LocalizedStringResource = "Tag Travel Photos"
@Parameter var photos: EntityCollection<PhotoEntity> // Previously: [PhotoEntity]
@Parameter var tag: String
func perform() async throws -> some IntentResult {
modelData.tagPhotos(ids: photos.identifiers, tag: tag) // Previously: tagPhotos(photos, tag: tag)
return .result()
}
}
Key points:
EntityCollection<PhotoEntity>replaces[PhotoEntity]photos.identifierscontains only IDs, not complete entity data- This avoids the performance cost of resolving hundreds or thousands of entities
- Your data model method only needs IDs to update records
SyncableEntity: Stable IDs across devices
([9:49](https://developer.apple.com/videos/play/wwdc2026/345/?time=589))
If your ID is already stable across devices, such as a server UUID or CloudKit record ID:
struct PhotoEntity: AppEntity, SyncableEntity {
var id: String // Already stable across devices; this is enough
}
If you use local IDs, pair them with a stable ID:
struct PhotoEntity: AppEntity, SyncableEntity {
var id: SyncableEntityIdentifier<String, String>
init(localID: String, stableID: String) {
self.id = SyncableEntityIdentifier(local: localID, stable: stableID)
}
}
Key points:
- The
SyncableEntityprotocol declares that the ID is stable across devices - If the ID itself is stable, adding the protocol is enough
SyncableEntityIdentifier<Local, Stable>pairs a local ID with a stable ID- Device-local code uses the local ID, while cross-device system behavior uses the stable ID
@UnionValue: Accept multiple types
([11:58](https://developer.apple.com/videos/play/wwdc2026/345/?time=718))
Allow one parameter to accept multiple entity types:
@UnionValue
enum TravelGalleryContent {
case landmarkCollection(LandmarkCollectionEntity)
case photoAlbum(PhotoAlbumEntity)
static let typeDisplayRepresentation: TypeDisplayRepresentation = "Travel Gallery"
static let caseDisplayRepresentations: [Cases: DisplayRepresentation] = [
.landmarkCollection: "Landmark Collection",
.photoAlbum: "Photo Album"
]
}
Key points:
- The
@UnionValuemacro generates the required type information and picker support - Each case wraps a different entity type
typeDisplayRepresentationis the label for the overall typecaseDisplayRepresentationsdefines how each option appears in the picker- It also appears as multiple options in the Shortcuts app
LongRunningIntent + CancellableIntent: Long-running tasks
([13:41](https://developer.apple.com/videos/play/wwdc2026/345/?time=821))
Let an intent run for more than 30 seconds and support cancellation:
struct UploadPhotoIntent: LongRunningIntent, CancellableIntent {
static let title: LocalizedStringResource = "Upload Photo"
@Parameter var photo: IntentFile
func perform() async throws -> some IntentResult & ProvidesDialog {
let result = try await performBackgroundTask {
let chunks = calculateChunks(for: photo)
progress.totalUnitCount = Int64(chunks)
for chunk in 1...chunks {
try Task.checkCancellation()
try await uploadChunk(chunk)
progress.completedUnitCount = Int64(chunk)
}
return "Upload complete!"
} onCancel: { reason in
cleanup(for: reason)
}
return .result(dialog: "\(result)")
}
}
Key points:
LongRunningIntentallows execution beyond the 30-second limitperformBackgroundTaskwraps work that needs a long time to run- The
progressobject comes fromProgressReportingIntent, and the system uses it to show progress - Progress is automatically shown as a Live Activity
Task.checkCancellation()checks whether the task has been canceledCancellableIntenthandles cleanup inonCancel- Users can tap the stop button in the Live Activity
ExecutionTargets: Control the execution process
([16:54](https://developer.apple.com/videos/play/wwdc2026/345/?time=1014))
Specify which process an intent runs in:
// Write operation: needs the main app
struct UpdateFavoriteIntent: AppIntent {
static var allowedExecutionTargets: ExecutionTargets { .main }
}
// Standalone download: runs in the extension
struct DownloadPhotoIntent: AppIntent {
static var allowedExecutionTargets: ExecutionTargets { .appIntentsExtension }
}
// Read-only display: runs in the widget extension
struct GetLandmarkStatusIntent: AppIntent {
static var allowedExecutionTargets: ExecutionTargets { .widgetKitExtension }
}
// Either one is fine: let the system choose
struct TagPhotosIntent: AppIntent {
static var allowedExecutionTargets: ExecutionTargets { [.main, .appIntentsExtension] }
}
Key points:
- The static
allowedExecutionTargetsproperty overrides the system’s heuristic choice .mainspecifies the main app.appIntentsExtensionspecifies the App Intents extension.widgetKitExtensionspecifies the Widget extension- You can specify multiple options and let the system choose among them
Key Takeaways
1. Landmark sharing apps
- What to do: Let users send landmarks from your app directly to Maps for navigation, or to Calendar as an event location.
- Why it is worth doing:
ValueRepresentationlets your entities carry system-known types such asPlaceDescriptorandEventDescriptor, integrating seamlessly with system apps such as Maps and Calendar. - How to start: Add
ValueRepresentationto your entity and export the corresponding Descriptor type.
2. Context-aware music apps
- What to do: Automatically recommend suitable playlists based on the user’s current activity, such as running, reading, or sleeping.
- Why it is worth doing:
RelevantEntitieslets you tell the system, “This playlist is relevant while running,” so the system can recommend it in a fitness app’s workout setup UI. - How to start: Define a context with
AppEntityContext, then register entities withRelevantEntities.shared.updateEntities(_:for:).
3. Batch photo management apps
- What to do: Let users tag hundreds or thousands of photos at once or add them to an album without lag.
- Why it is worth doing:
EntityCollectionpasses only IDs, avoiding the performance cost of resolving large numbers of entities and making batch operations complete quickly. - How to start: Change an array-receiving
@ParametertoEntityCollection<YourEntityType>, then use theidentifiersproperty to get the ID array.
4. Cloud-synced notes apps
- What to do: Let users say “Add this to my to-do list” for a note on iPhone, then say “Mark it complete” for the same note on Mac.
- Why it is worth doing:
SyncableEntitylets Siri refer to the same entity across devices and enables true cross-device conversations. - How to start: Make sure the entity has a stable cross-device ID, such as a server UUID or CloudKit record ID, then add
SyncableEntity.
5. Large file upload apps
- What to do: Let users trigger large file uploads from a Widget, show real-time progress, and cancel at any time.
- Why it is worth doing:
LongRunningIntentlets tasks run longer than 30 seconds, progress is automatically shown as a Live Activity, andCancellableIntentenables graceful cleanup on cancellation. - How to start: Implement
LongRunningIntentandCancellableIntent, wrap the work withperformBackgroundTask, and updateprogressregularly.
Related Sessions
- Code-along: Make your app available to Siri - Build your app’s Siri experience step by step
- Validate your App Intents adoption with AppIntentsTesting - Use the new framework to test your App Intents
- Explore advanced App Intents features for Siri and Apple Intelligence - A deeper look at advanced App Intents features for Siri and Apple Intelligence
- Bring your in-app features to Siri, Search, and Shortcuts - Advanced App Intents topics
Comments
GitHub Issues · utterances