App Schema Domains: the complete map of what Siri can actually do¶
Part 16 · Adjacent capabilities · Reference 02
Version floor: the 27 releases — iOS 27, iPadOS 27, macOS 27, watchOS 27, visionOS 27, tvOS 27,
with Xcode 27. That is the floor for the new material in this guide: LongRunningIntent,
ExecutionTargets, EntityCollection, SyncableEntity, RelevantEntities,
OwnershipProvidingEntity, IndexedEntityQuery, ValueRepresentation, @UnionValue as an
input parameter, and the .system.searchInApp schema name. It is not the floor for the
schema system itself. App schema domains predate this release — session 240's presenter points
readers at "my video from WWDC24" for how schemas work — and three of the load-bearing types in
this guide are considerably older: StringSearchCriteria is iOS 17.2 / macOS 14.2,
SnippetIntent is iOS 26.0, and the .system search schema was, in Apple's own words,
"introduced in iOS 17." Every API below carries its earliest OS where we could establish one,
and a 🔴 GAP box where we could not.
SDK-interface pass, 2026-07-29. Everything above was re-checked against the SDK module
interfaces in notes/sdk-interfaces/ — AppIntents-26.5-macos.swiftinterface and, from the real
macOS 27.0 SDK in Xcode 27 beta, AppIntents-27.0-macos.swiftinterface (16,826 lines). Claims
confirmed there are marked ✅ SDK-verified with file:line citations; that marker outranks
every other evidence class in this guide, because it is the declaration the compiler sees. The
headline floors hold: LongRunningIntent, the execution-targets machinery, EntityCollection,
SyncableEntity, RelevantEntities, OwnershipProvidingEntity, IndexedEntityQuery, the
@UnionValue input-parameter machinery and .system.searchInApp are all anyAppleOS 27.0 in the
interface, and none of them exists in the 26.5 interface. One refinement: ValueRepresentation is
a typealias for IntentValueRepresentation, which the 27.0 interface annotates anyAppleOS 26.4
— §13.1, whose naming hazard that pass resolves outright.
⚠️ One version caveat you will trip over reading Apple's own material. Session 345 says
"our 2027 releases" — three separate times. Sessions 240 and 343 say "the 27 releases."
Session 241 says "our 2027 release." These are the same OS family named two ways: the version
number and the marketing calendar year. "2027 releases" is not a claim about a release after
iOS 27. The corroboration is that BarcodeReaderTool and OCRTool, announced at the same
conference, carry documented availability "iOS 27.0+ Beta, macOS 27.0+ Beta." Treat every
year label in this space as soft and every API name as the solid part.
What this covers¶
Choose an App Intents schema domain, apply the intent/entity/enum macros, and supply the required representations and parameters. The domain inventory distinguishes Siri-facing, single-purpose, and Shortcuts-only schemas; unsupported categories need a different integration path.
What this does not cover¶
Related references: Part 16 guide 03, Part 16 guide 04, Part 2 guide 04.
What you need¶
- Xcode 27. The schema tooling — autocomplete of schemas by domain, the missing-schema build error, and the Fix-Its that generate a schema adoption — is an Xcode feature, not a runtime one. You get it whether or not you have a device that runs Apple Intelligence.
- A device or simulator on the 27 releases for anything involving Siri end to end.
import AppIntents. Everything in §5 lives there. The Spotlight material additionally needsimport CoreSpotlight;ValueRepresentationwith places needsGeoToolbox.- Realistic expectations about Siri availability on betas.
SystemLanguageModel.default .availabilityreturning.appleIntelligenceNotEnabledunless "Siri"/"Press Side Button for Siri" is switched on is a bug, acknowledged by an Apple Frameworks Engineer on forum thread 836760, not a design constraint you should build around. It affects Foundation Models more than App Intents, but it will confuse your device testing. See Part 1 guide 02.
⚠️ Read this before you trust a symbol name below¶
SDK citations retain the 2026-07-29 macOS captures AppIntents-26.5-macos.swiftinterface and AppIntents-27.0-macos.swiftinterface. A symbol missing there may still exist on iOS. The SDK confirms AppEntity.ValueRepresentation is an alias for IntentValueRepresentation (§13.1).
Published session code, transcript reconstructions, Apple-staff replies, and community findings remain attributed individually. See the shared evidence conventions.
Contents¶
- Three systems people conflate
- Discovery versus action — the framing everything else follows from
- The macro system, and the build errors it generates
- The three tiers, and what each one reaches
- The complete enumeration — all 23 domains
- The absences — what has no domain at all
- Deprecations, and where generic search went
.system.searchInApp— the escape hatch- Decision tree: which route is open to you
- Query protocols and how entity resolution works
- Shaping the response: dialog, questions, and snippets
- Donations, confirmations, and entity ownership
- The new execution model
- Silent failures
- Testing: the four-stage ladder
- Gap register
- Sources
1. Three systems people conflate¶
Before the map, the terrain. A large share of the developer confusion in this area — and the forum record makes it very visible — comes from treating three different mechanisms as one feature. They have different requirements and, crucially, different degrees of openness.
(A) App Shortcuts — voice invocation of your own intents¶
Fully open. Any AppIntent you write, exposed through an AppShortcutsProvider, is invocable
by voice. No schema required, no indexing required, no whitelist.
The cost is a phrase shape: the user generally has to name your app. "How far have I travelled in TripLog?" rather than "How far have I travelled?"
Session 240 is explicit that this baseline is real and valuable on its own:
✅ VERIFIED (transcript, WWDC26 240) — "When you define an app intent, that action can show up across the system in places like Shortcuts, Spotlight, Widgets, and more. … even without Siri."
This is the fallback every blocked developer lands on. Forum thread 837249 describes arriving here and finding it unnatural — a cycling app, hands on the bars, having to say the app's name in every request. That complaint is legitimate and it has no first-party answer. It is also, for many apps, the only complete answer available today.
(B) App schema domains — "Siri, do this thing" without naming the app¶
Restricted. For Siri to route a generic, un-app-qualified request to your app, your intent or
entity must conform to one of Apple's predefined schemas via @AppIntent(schema:) /
@AppEntity(schema:) / @AppEnum(schema:). The schema fixes the parameter list and the semantics,
so the system knows what your type means rather than merely what it is called.
Session 240 puts the relationship precisely:
✅ VERIFIED (transcript, WWDC26 240) — "Just like entities use app schemas to be understood, actions use schemas to become executable by Siri. Think of schemas as a specialization of App Intents. They're still App Intents, but shaped in a way that Siri knows how to process."
There are 23 domains. §5 lists all of them. The surface is closed — there is no mechanism to define your own domain, register a custom schema, or petition at runtime. If your app's core concept is not on the list, this route is unavailable to you. That structural fact is the cause of essentially every thread in the forum cluster.
(C) On-screen awareness — "this"¶
Restricted differently, and the least documented of the three. Evidence from forum thread 838329, instrumented on iOS 27 beta 3, indicates the system takes two separate paths:
| Request shape | Path taken | Does it call your EntityQuery? |
|---|---|---|
| "Describe this image" | screenshot / OCR | No |
| "Create a note for this" | screenshot / OCR | No |
| "Send this to \<contact>" | entity resolution | Yes — but failed for a custom entity |
| Hand-off to another app | entity resolution | Yes — fired, then stalled |
Community-measured, forum thread 838329 (FrankSchlegel, 17 Jul 2026), iOS 27 beta 3, on device. The
entities(for:)never-fires observation is from the poster's own instrumented logging. Not an Apple statement.
The operational consequence is sharp and worth stating before anyone spends a week on it: descriptive on-screen questions never consult your entities. If your app's Siri ambition is "answer questions about what's on screen," entity plumbing is the wrong tool — the system is reading pixels. Guide 03 in this part covers this in full.
The rule that falls out¶
Discoverability is open. Actionability is whitelisted.
Siri can see your custom entity — indexing it into Spotlight is enough for that. It will not reliably act on it, or move it across an app boundary, unless it is schema-typed. This is not a bug report; it is the documented architecture, and §2 quotes Apple saying so.
The community reached the same conclusion independently. Forum thread 829586 — 14 replies and about a thousand views, the most-discussed thread in this cluster — records a distinction attributed to the Apple Intelligence Group Lab:
Community-reported (thread 829586, developer paraphrase of a Group Lab answer; not a written Apple statement): 1. Entity discoverability does not require conforming to whitelisted schema domains. 2. Siri can only take actions that do conform to whitelisted schema domains.
That thread also notes that Apple's own TrailEntity hiking sample maps to no published
domain, so Siri cannot help users compare routes in Apple's own example. The developer filed
FB23018652; DTS characterised it as an enhancement request, not a bug.
2. Discovery versus action — the framing everything else follows from¶
The cleanest statement in the entire documentation set is on the page "Making actions and content discoverable by Apple Intelligence." It splits the problem into two layers that are satisfied by two completely different mechanisms at two different times:
✅ VERIFIED (docs) — Discovery is a runtime concern, satisfied by indexing. "Submit your entities to the Spotlight semantic index so the system indexes your app's content and matches it to requests."
✅ VERIFIED (docs) — Action is a build-time concern, satisfied by schemas. Schema macros plus the required properties are what let Apple Intelligence interpret a request and invoke your intent.
And then the sentence that resolves the whole forum cluster:
✅ VERIFIED (docs) — "Without both layers, Apple Intelligence cannot act on user requests involving your entities."
Read that carefully. It does not say indexing is optional. It says neither layer alone is sufficient. An app that indexes entities but adopts no schema gets discovery without action. An app that adopts a schema but never indexes gets an executable action that Siri cannot find the right operand for.
Why a schema is needed on top of an entity¶
Session 240 explains what the schema buys in terms of what Siri can reason about:
✅ VERIFIED (transcript, WWDC26 240) — "modeling an entity is the first step, but on its own, that's not enough for Siri to be able to find it or talk about it. For Siri to understand what an entity is, what category of thing it represents, your entity needs to conform to an
AppSchema. … Instead of treating your app like a black box, Siri can reason about what the user is talking about."
The same session gives the definition of an entity that prevents the most common misunderstanding — that entities are a data model you must migrate to:
✅ VERIFIED (transcript, WWDC26 240) — "App entities describe three important things. What the thing is, how it's identified, and which properties matter, like a title, a date, or some text." … "They're not a new data model. They're a way of describing your existing content so the system can understand it."
And the mapping is mundane on purpose: "If you have a calendar app, each event is an entity. If you have a mail app, each message is an entity. And if you have a photos app, each photo and each album is an entity." (✅ VERIFIED, transcript, 240.)
What a domain is¶
✅ VERIFIED (transcript, WWDC26 240) — "schemas are grouped into
AppSchemadomains. Each domain represents a category of tasks, such as mail, photos, messages, and more. … Think of domains as categories of contracts between your app and Siri."
"Contract" is the right word and it is worth taking literally. A contract has terms you do not get to renegotiate: the parameter names, the parameter types, sometimes even your identifier type (§3.2). In exchange, Siri undertakes to understand the natural language and route it to you. The trade is real and it is not symmetric — you give up modelling freedom, and what you get back is language understanding you could not have built.
⚠️ Where the docs contradict the observed behaviour¶
One documentation sentence cuts against all of the above, and you should know about it before you read it somewhere and act on it. The "Providing contextual cues to Apple Intelligence and Siri" page says, of schema adoption for on-screen annotation:
✅ VERIFIED (docs) — "Schema application is optional but recommended for consistency."
The observed behaviour on iOS 27 beta 3, per thread 838329, is that a non-schema entity was never
resolved for hand-off — entities(for:) never fired for the Siri paths — while the schema-typed
.files.file entity resolved and transferred correctly.
🟡 RECONSTRUCTED interpretation. Both statements can be true if "optional" governs annotation/reference — pointing at a thing — while schemas govern transfer — moving its payload. That reading is consistent with the discovery/action split above. But no Apple page says this, and a developer reading "optional but recommended" will reasonably conclude the custom-entity route is supported, and then lose days. The original poster asked Apple this exact question:
Community quotation, thread 838329 — "I wonder if this more generic approach shouldn't also work... Is on-screen consumption intended to be limited to the predefined assistant schemas, or should a custom
AppEntity+Transferablealso be a supported way to expose arbitrary on-screen content?"
It is still unanswered. Apple's DTS engineer replied to the thread and routed the developer to Feedback Assistant (FB23813341) without giving API guidance. The working answer in that thread came from another developer.
Safe default while this is open: assume schema-typed means actionable and non-schema means discoverable-only. Design so that the schema-typed path is the one your feature depends on, and treat any custom-entity behaviour that happens to work as a bonus you have not been promised.
3. The macro system, and the build errors it generates¶
Three macros, one per kind of thing. This is the entire adoption surface.
import AppIntents
@AppEntity(schema: .photos.asset) // content — a noun your app owns
@AppIntent(schema: .photos.openAsset) // actions — a verb Siri can execute
@AppEnum(schema: .photos.assetType) // property value sets — a closed vocabulary
✅ VERIFIED (docs) — all three spellings from the "Making actions and content discoverable by Apple Intelligence" page and the app-schema-domains index page.
✅ SDK-verified — all three macros are declared in the 27.0 interface, each
@available(iOS 18.0, macOS 15.0, tvOS 18.0, watchOS 11.0, visionOS 2.0, *):
macro AppIntent<T>(schema:) (AppIntents-27.0-macos.swiftinterface:10944),
macro AppEntity<T>(schema:) (:10769), macro AppEnum<T>(schema:) (:10625). The schema:
argument resolves through an AppSchema namespace (:10593) that exists only in the 27.0 SDK
— the 26.5 interface spells the same schemas through an AssistantSchemas namespace, which the
27.0 interface marks deprecated throughout. Same schemas, new front door; the macro spellings you
write are unchanged.
Symbol paths follow AppSchema.<Domain><Kind>.<name>. So the dot-syntax .mail.sendDraft you
write at the macro call site resolves to AppSchema.MailIntent.sendDraft, documented at
/documentation/appintents/appschema/mailintent/senddraft. ✅ VERIFIED (docs) — this is how the
documentation URLs are structured, which is also the fastest way to check whether a schema you
half-remember actually exists: construct the URL.
⚠️ One naming mismatch to know about before you go URL-hunting. The journaling domain's page
lives at app-schema-domain-journaling and is titled "Journaling", but the symbol is
AppSchema.JournalIntent — singular, no "-ing" — and the dot-syntax is .journal. ✅ VERIFIED
(docs). Guessing .journaling will not compile.
3.1 Schemas are enforced at build time, with Fix-Its¶
This is the pleasant surprise of the system. You do not discover a missing property at runtime in a Siri transcript; you discover it when you press Build.
✅ VERIFIED (docs) — "If your type is missing a required property, Xcode generates an error at build time with a Fix-It that adds it for you."
The required-property set is per schema. For .photos.asset:
// Required by @AppEntity(schema: .photos.asset)
var displayRepresentation: DisplayRepresentation
var id: Int
var creationDate: Date?
var assetType: PhotoAssetType?
var isFavorite: Bool
var isHidden: Bool
✅ VERIFIED (docs) — the "Making actions and content discoverable" page lists exactly these.
And for .photos.openAsset:
// Required by @AppIntent(schema: .photos.openAsset)
var target: <EntityType>
func perform() async throws -> some IntentResult
✅ VERIFIED (docs).
3.2 Schemas dictate your identifier type — this is not a formality¶
Look again at .photos.asset: var id: Int.
Not var id: ID. Not "some Hashable". Int. If your photo model is keyed by UUID or by a
CloudKit record name, adopting .photos.asset means producing a stable Int for every asset and
being able to go back the other way — because EntityQuery.entities(for:) will hand you [Int]
and expect entities in return.
That is a real modelling constraint that lands in your persistence layer, and it is worth discovering in an afternoon of design rather than three weeks into an implementation. Session 344, building a calendar app, hit the friendlier version of this and said so out loud:
🟡 RECONSTRUCTED from narration (WWDC26 344) — "set the id type to
UUIDto match the data model" for@AppEntity(schema: .calendar.calendar).
So .calendar.calendar is not pinned to Int the way .photos.asset is. The identifier type
is per-schema and you have to look it up. There is no general rule.
🔴 GAP — the per-schema required-property tables. We have verified required-property lists for
exactly two schemas: .photos.asset and .photos.openAsset (from the docs page above), plus the
parameter lists of .clock.createTimer, .audio.addToPlaylist, .system.searchInApp and
.system.open (from Apple code samples, §5 and §8). The other ~180 schemas' required properties
are not enumerated anywhere in our corpus. What would resolve it: the per-schema documentation
pages under /documentation/appintents/appschema/<domain>intent/<name>, read one at a time. The
27.0 SDK interface was checked on 2026-07-29 and does not settle it: the interface encodes
each schema only as an opaque string accessor — AppSchema.Intent("MailSendDraftIntent") and the
like — not as a property list; the requirements live in the macro's external definition, outside
the module interface. Safe default: do not plan your data model
around a schema you have not opened in Xcode. Type the snippet trigger (§3.4), let Xcode scaffold
the real property list, and then decide whether adoption is cheap.
3.3 ⚠️ Schemas come in conversational sets — a build error you will not expect¶
This is the most surprising behaviour in session 240 and it appears in no documentation page we found.
✅ VERIFIED (transcript, WWDC26 240) — "we adopted the
sendMessageschema in UnicornChat. That works great. People can send messages with Siri. But now, let's try to build. And we get a build error. Xcode is telling us that while we adoptedsendMessage, we haven't adopted the relateddraftMessageschema. This is important because some Siri scenarios require more than one schema to deliver a complete experience."
The reason is confirmation flow, and once stated it is obvious: Siri drafts, shows you the draft, and sends on your approval. Without a draft schema there is nothing to show.
✅ VERIFIED (transcript, WWDC26 240) — "This isn't just a compiler error, it's a design hint. Xcode knows that if your app can send messages with Siri, it also needs a way to draft messages, especially when confirmation is required. So instead of failing silently at runtime, the build system surfaces this early."
And there is a generated fix:
✅ VERIFIED (transcript, WWDC26 240) — "If we click into the error, Xcode offers a fix-it. Xcode generates a sample adoption of the
draftMessageschema. This gives us an intent definition, the required parameters and a stub implementation. All wired correctly."
One detail about the generated stub that matters for concurrency: it "needs to run on the main
actor" because it "mutates UI state" — it opens the message-creation view. ✅ VERIFIED
(transcript, 240). If you are adopting schemas into an actor-isolated architecture, expect
@MainActor on the intents that present UI.
The generalization Apple states:
✅ VERIFIED (transcript, WWDC26 240) — "if a Siri experience depends on multiple schemas, Xcode will tell you, show you what's missing, and help you generate the right remaining steps."
🔴 GAP — the full co-requisite graph. Exactly one pair is demonstrated anywhere in our
corpus: .messages.sendMessage ⇒ .messages.draftMessage. Which other schemas have
co-requisites, and what they are, is unknown. What would resolve it: adopting each schema in a
scratch project and reading the build errors. The 27.0 SDK interface was checked on 2026-07-29
and does not settle it — co-requisites are not encoded in the module interface. Safe default: budget for the possibility that adopting any "commit an
action" verb drags in its "prepare the action" sibling. Plan schema adoption in pairs, not
singletons.
3.4 The Xcode snippet trigger — <domain>_¶
The discovery mechanism is a code snippet keyed by domain prefix. Type the domain name, an underscore, and Xcode offers every schema in that domain.
✅ VERIFIED (docs) — from the system-and-in-app-search domain page: "Xcode generates a template implementation when you type
system_and select a schema from the suggestions list."
Session 344 walks the same workflow for .calendar and confirms the convention generalizes:
✅ VERIFIED (transcript, WWDC26 344) — "I'll type
calendar_in the editor. Xcode offers every schema in the Calendar domain, right in autocomplete. Since the goal is a calendar entity, I'll selectcalendar_calendar."✅ VERIFIED (transcript, WWDC26 344) — "The snippet fills in the structure: the
@AppEntitymacro, properties,DisplayRepresentationand query stubs."
For intents specifically it "scaffolds the intent with the @AppIntent macro, the schema, all
the parameters the schema requires, and a perform stub." ✅ VERIFIED (transcript, 344).
Snippet names verified in session 344, all following <domain>_<schemaName>:
| Snippet you type | What it scaffolds |
|---|---|
calendar_calendar |
@AppEntity(schema: .calendar.calendar) |
calendar_attendee |
attendee entity |
calendar_attendeeStatus |
@AppEnum with "all the cases the schema supports" |
calendar_attendeeType |
@AppEnum for attendee kind |
calendar_event |
event entity |
calendar_createEvent |
@AppIntent(schema: .calendar.createEvent) + params + perform stub |
calendar_updateEvent |
update intent |
system_ (prefix) |
every .system schema |
✅ VERIFIED (transcript, 344) for the calendar_* rows; ✅ VERIFIED (docs) for system_.
This is the single best way to use §5 of this guide. The enumeration below tells you whether a schema exists; the snippet tells you what it requires. Look it up here, type it there.
3.5 The schema @AppEnum rule: subsettable, but non-empty¶
Schema enums behave differently from your own enums, and session 344 states the rule cleanly:
✅ VERIFIED (transcript, WWDC26 344) — "The schema defines the set of possible cases, and my app adopts the ones that apply." … "The snippet comes with all the cases the schema supports. CometCal's model already maps directly, so no changes are needed, but if an app uses different terminology, simply map the existing model to the schema's cases so Siri can recognize the shape."
And the floor:
✅ VERIFIED (transcript, WWDC26 344) — "The schema requires at least one case to describe what kind of attendee this is and since CometCal's attendees are all people, I'll add a
personcase."
So: you pick from a fixed vocabulary, you may not invent cases, and you must supply at least one. If your app's vocabulary differs from Apple's — you call them "collaborators", the schema calls them "attendees" — you map, you do not extend.
3.6 The schema is a floor, not a ceiling¶
A genuinely reassuring rule, and the one most likely to unblock an adoption decision. From
session 344, on building a large EventEntity:
✅ VERIFIED (transcript, WWDC26 344) — "The schema defines which properties are required and which are optional. The essentials like
titleorstartDateare straightforward to wire up. Optional properties that my app doesn't use, liketravelTimeorvirtualLocation, can simply stay unset. Properties that aren't part of the schema but exist on the data model, likeisFavorite, can also be added to the entity."
Three rules, in order of how often they are misunderstood:
- Required → wire it up. Non-negotiable; the build fails otherwise.
- Optional and unused → leave unset. You do not have to synthesize data you do not have. A calendar app with no travel-time concept does not invent one.
- Not in the schema but on your model → add it anyway. The entity may carry properties the schema knows nothing about. Siri will not reason about them, but Shortcuts, your own code and the Spotlight index can still use them.
Rule 3 is the important one. Adopting a schema does not force you to throw away the parts of your model that make your app yours. It forces you to also speak Apple's vocabulary for the parts Apple has vocabulary for.
4. The three tiers, and what each one reaches¶
Apple's own index page groups the 23 domains into three tiers. The tier determines where the schema is usable, and this distinction is the easiest thing in the whole area to miss.
| Tier | Count | Domains | Reach |
|---|---|---|---|
| Primary | 13 | audio calendar camera clock files mail maps messages notes phone photos reminders system |
Apple Intelligence + Siri + Shortcuts |
| Single-purpose | 2 | assistant visualIntelligence |
One specific system surface each |
| Shortcuts-specific | 8 | books browser journal presentation reader spreadsheet whiteboard wordProcessor |
Shortcuts app — per the docs' own grouping |
✅ VERIFIED (docs) — the grouping, the tier labels, and the membership of each tier come from
/documentation/appintents/app-schema-domains, fetched 2026-07-27.
⚠️ Why the third tier matters more than it looks¶
A developer who adopts, say, .wordProcessor.createPage expecting Siri to route natural-language
requests to it is adopting a Shortcuts-tier schema. The documentation's own taxonomy places
these outside the Apple Intelligence / Siri tier.
If you are building an iWork-class app, this is the difference between "Siri can create a page in my app when someone asks" and "there is a Shortcuts action a user can wire into an automation." Both are worth having. They are not the same thing, and the second one will not satisfy a product requirement written as the first.
🟡 RECONSTRUCTED — the exact capability each tier confers. We have the three-tier grouping and its labels from the index page. We did not find any page that states in prose what capability each tier grants. The "Reach" column above is inference from the taxonomy labels, not a quoted claim. What would resolve it: a documentation page or session statement defining tier semantics, or empirical testing — adopt a Shortcuts-tier schema and try an un-app-qualified Siri request on device. The 27.0 SDK interface was checked on 2026-07-29 and does not settle it — it encodes domain membership (§5.4) but says nothing about what each tier reaches. Safe default: if your feature must work through Siri without the user naming your app, plan on a primary-tier domain, and treat any Shortcuts-tier behaviour beyond the Shortcuts app as a bonus.
Corroboration from the domain prose¶
Two independent signals support the tier reading, both ✅ VERIFIED (docs):
- Primary-domain pages use Apple Intelligence and Siri language directly. The
.notespage: "Make your note-taking app's actions available to Apple Intelligence and Siri by adopting schemas for common note actions." The.phonepage: "Make your phone app's actions available to Apple Intelligence and Siri by adopting schemas for calling actions." - The single-purpose pages name their one surface explicitly —
.assistantregisters a side-button action,.visualIntelligence"connects your app to the camera control."
The Shortcuts-tier pages, in the material we captured, do not carry the Apple Intelligence framing. That is consistent with the taxonomy, and it is absence of evidence rather than a statement, so it strengthens the inference without settling it.
5. The complete enumeration — all 23 domains¶
This is the artifact. Every domain, every documented leaf schema, grouped by tier.
How to read the evidence here¶
Every name below traces to an Apple documentation page read through the sosumi.ai mirror during
the research pass of 2026-07-27, recorded in notes/web/app-intents-siri-schemas.md §3. That pass
states that each domain page was fetched individually at
https://sosumi.ai/documentation/appintents/app-schema-domain-<name>.
One honesty note about how strongly verified each row is. The pass's own source-inventory table itemizes eleven domain pages explicitly by URL — mail, files, photos, camera, browser, reader, journaling, books, whiteboard, assistant, and system-and-in-app-search — plus the index page. The remaining twelve domains rest on the pass's blanket statement that all were fetched, not on an itemized URL row. We mark the whole enumeration ✅ VERIFIED (docs) because it all comes from the same class of artifact, and we flag the distinction here rather than burying it, because if any row in §5 turns out to be wrong it will be in the un-itemized twelve.
Update, 2026-07-29 — the enumeration is now SDK-checked, and the prediction above came true.
Every domain below was censused against the AppSchema namespace in
AppIntents-27.0-macos.swiftinterface, counting the leaf accessors each domain's marker protocol
declares. The docs-derived enumeration is confirmed for eighteen of the 23 domains and corrected
for five: clock (14 intents / 3 entities / 3 enums — the docs pass missed the entire stopwatch
surface), mail (1 enum, category), books (10 enums, not 12), presentation (15 intents —
addVideoToSlide exists), and system (3 intents once searchInApp is counted). Corrections are
applied inline and in §5.4, each with its citation.
Two SDK facts the documentation pages do not carry, both ✅ SDK-verified:
- Eight of the thirteen primary domains are new API surface at 27.0. The
audio,calendar,clock,maps,messages,notes,phoneandremindersaccessors are all@available(anyAppleOS 27.0, *)and none of them exists in the 26.5 interface;mail,files,photos,camera,systemand the Shortcuts tier carry@available(iOS 18.0, macOS 15.0, visionOS 2.0, *). Adopting a new-domain schema pins your deployment floor to the 27 releases; the 2024-era domains reach back to iOS 18 / macOS 15. - Every domain accessor checked is watchOS- and tvOS-unavailable. The two single-purpose
domains are narrower still:
.assistantis@available(iOS 26.2, *)and unavailable on every other platform (:12934-12940), and.visualIntelligenceis@available(iOS 26.0, macOS 27.0, *)with tvOS, watchOS and visionOS unavailable (:13066-13071).
Counts are exact at the row level: they count declared leaf schemas and exclude container/protocol
symbols such as AppSchema.MailIntent. Deprecated schemas are included in the counts and
marked. They remain macOS-surface counts — an iOS interface could conceivably differ.
5.1 Primary domains (13)¶
These reach Apple Intelligence, Siri and Shortcuts. If your app's core concept is here, this is where the whole thing works.
.audio — 7 intents · 18 entities · 6 enums¶
The largest entity surface of any domain, by a factor of three.
Intents: playAudio, addToLibrary, addToPlaylist, createStation, recognizeAudio,
updateAudioAffinity, warmupAudioQueue
Entities: album, algorithmicRadioStation, ambientSound, artist, audiobook,
classicalMusicRecording, liveRadioStation, newsBrief, newsProvider, playlist,
podcastCollection, podcastEpisode, podcastShow, radioShow, radioShowEpisode, song,
songCollection, warmupAudioQueueResult
Enums: activity, affinityState, appViewIdentifier, invocationSource,
playbackAttributes, queueInsertionLocation
What the shape tells you. Eighteen entities for seven verbs is a domain built around
naming things precisely rather than doing many things. classicalMusicRecording as a distinct
entity from song is a strong signal about how much modelling effort went in — classical music
has works, movements, performers and conductors that a pop-song schema cannot express, and someone
decided that mattered.
warmupAudioQueue / warmupAudioQueueResult is a latency-optimization pair with no analogue in
any other domain. Nothing else in the 23 domains has a "get ready, I'm about to ask" verb. That
tells you where Apple thinks the volume is: "play X" is plausibly the highest-frequency Siri
request category there is, and shaving the warm-up off it was worth a dedicated schema.
Two .audio schemas appear in Apple's own code samples with their parameter lists, which is
unusually good evidence:
See the canonical example.
Note audioEntity: AudioEntity — session 343 describes AudioEntity as "a UnionValue type that
includes both songs and playlists" (✅ VERIFIED, transcript, 343). A schema parameter can itself
be a union type. That is worth internalizing before §13.4.
And .audio.song on the entity side:
See the canonical example.
.calendar — 3 intents · 3 entities · 4 enums¶
Intents: createEvent, deleteEvent, updateEvent
Entities: attendee, calendar, event
Enums: attendeeStatus, attendeeType, eventSpan, eventStatus
Note the absence of any query or search intent. Reading the calendar is not a schema action. Three verbs, all mutations. If you want "when is my next meeting?" to work, that comes from entity resolution over the Spotlight index (§10), not from an intent you adopt.
This is the general pattern across primary domains and it takes a moment to accept: intents are
for changing things; questions are answered by entities. Session 240's first example of the new
Siri is "When and where is my next meeting?" and it is answered by Siri "understanding what a
meeting is in your app, which meeting is relevant, and which properties to return" — no
queryEvents schema involved. ✅ VERIFIED (transcript, 240).
.calendar is the best-documented domain in our corpus because session 344 builds it end to end.
Verified details from that build:
.calendar.calendarentity —idset toUUIDin the sample;DisplayRepresentationwith acalendarSF Symbol; conforms toIndexedEntity..calendar.attendee— modelled as aTransientAppEntity(§10.5), carrying anIntentPerson, a status enum, a type enum, and anisOptional: Bool..calendar.event— composes aCalendarEntityand an array ofAttendeeEntity; uses Foundation'sCalendar.RecurrenceRule; and has two union-typed properties:
| Property | Union members |
|---|---|
location |
PlaceDescriptor (GeoToolbox) or String |
| alarms | Duration or Date |
🟡 RECONSTRUCTED — these come from session 344's narration, which had no published code
block. The member types are stated in words; the exact union type names shown on screen were
rendered in our notes as LocationUnion / AlarmUnion and should be treated as provisional.
The 27.0 interface was checked on 2026-07-29: no LocationUnion or AlarmUnion type exists in
the module, which is consistent with these being app-side @UnionValue declarations whose
names are the sample author's, not the SDK's. Name yours whatever you like.
eventStatusappears in narration asEventEntityStatus. 🟡 The doc index says the enum schema is.calendar.eventStatus; the generated Swift type name in the sample wasEventEntityStatus. Those are consistent — schema name versus your type's name — but do not assume the generated name.
.camera — 5 intents · 0 entities · 3 enums¶
Intents: openInCaptureMode, setDevice, startCapture, stopCapture, switchDevice
Enums: captureDevice, captureDuration, captureMode
The only domain with zero entities. A pure control surface: there is nothing to reference,
only things to do. That is the correct shape for a camera — a photo becomes referenceable the
moment it exists, and at that point it is a .photos.asset, not a camera object.
Practical consequence: .camera adoption requires no data modelling, no Spotlight indexing, and no
query protocol. It is the cheapest primary domain to adopt in the whole list. If you ship a camera
app, there is very little reason not to.
.clock — 14 intents · 3 entities · 3 enums¶
Intents: createAlarm, updateAlarm, snoozeAlarm, dismissAlarm, deleteAlarm,
createTimer, updateTimer, pauseTimer, resumeTimer, cancelTimer, startStopwatch,
stopStopwatch, lapStopwatch, resetStopwatch
Entities: alarm, stopwatch, timer
Enums: alarmTriggerState, stopwatchState, timerState
✅ SDK-verified (AppIntents-27.0-macos.swiftinterface:12013-12166) — and this is the largest
single correction the SDK pass makes to the docs-derived map. The domain pages gave .clock ten
verbs over two nouns; the interface adds a complete stopwatch lifecycle — four verbs, a
stopwatch entity and a stopwatchState enum — that appears on no documentation page in our
corpus.
Fourteen verbs over three nouns: full lifecycle control of alarms, timers and stopwatches, with
snooze and dismiss broken out as first-class actions rather than folded into update. That
split exists because they are the two things people say to a ringing alarm, and Siri needs them to
be distinct utterances mapping to distinct calls.
.clock.createTimer's parameter list is verified from Apple's own code:
See the canonical example.
Three things fall out of that block beyond .clock:
duration: Duration— a schema parameter typed as Swift'sDuration. This corroborates session 345's claim thatDurationis now a natively supported@Parametertype (§13.6).$label.requestValue(_:)— the macro projects each parameter as$nameand lets you ask a clarifying question mid-perform(§11.2).some ReturnsValue<TimerEntity>— a return type composition worth noting: schema intents can return entities, not justIntentResult.
.clock also has a special relationship with interaction donations: the donation mechanism's
"ongoing activity" behaviour is scoped, by name, to stopwatch verbs in this domain and navigation
verbs in .maps — and the four stopwatch schemas above are exactly the verbs that scoping needs,
which is mutual corroboration. One wording mismatch to note: session 343's scoping quote says
"stop, start, pause, or lap"; the interface's fourth verb is resetStopwatch, and there is no
pauseStopwatch. See §12.1.
.files — 5 intents · 1 entity · 0 enums¶
Intents: createFolder, deleteFiles, moveFiles, openFile, renameFile
Entities: file
Tiny domain, outsized importance. .files.file is the schema that forum thread 838329 found to
be the only working route for handing an on-screen item to another app. The mechanism is
FileEntityIdentifier as the entity's identifier type plus FileRepresentation as the transfer
representation — and neither of those is mentioned on the .files domain page. That linkage
came entirely from the forum thread. It is a documentation gap worth knowing about, because
nothing on the domain page would lead you to the recipe.
The full recipe, its caveats, and why the obvious alternative silently fails are guide 03 in this part. The one-line version, because it belongs in any map of this territory:
Community-verified, thread 838329, confirmed on device on iOS 27:
@AppEntity(schema: .files.file)+FileEntityIdentifier.file(url:)+FileRepresentationworks for "send this to \<contact>". Plain customAppEntity+Transferable+DataRepresentationnever resolved —entities(for:)was never called. Caveat: this verifiedFileRepresentationhand-off requires a real file payload, so transient renders must be written out before export. That is not an identity limitation:FileEntityIdentifier.draft(identifier:)can represent a document before it is materialized, but the draft deliberately has nofileURLto transfer.2
.mail — 12 intents · 5 entities · 1 enum¶
Intents: createDraft, updateDraft, saveDraft, openDraft, deleteDraft, sendDraft,
openMessage, replyMail, forwardMail, updateMail, archiveMail, deleteMail
Entities: account, draft, mailbox, message, thread
Enums: category
✅ SDK-verified, with two facts the domain page misses: the enum category exists — and has
existed since the iOS 18-era surface (AppIntents-27.0-macos.swiftinterface:12879-12888) — and
openDraft, openMessage and the thread entity are anyAppleOS 27.0 additions to an otherwise
iOS 18-era domain (:12784-12796).
The reference model for what "complete" schema coverage looks like. A clean two-phase CRUD
design: six verbs for the draft lifecycle, six for the message lifecycle, and the entity set covers
every noun a mail app has — including account and mailbox, which many apps would treat as
configuration rather than content.
The draft/message split is the same pattern as .messages' draftMessage/sendMessage
co-requisite (§3.3), and it is the strongest hint available about what the co-requisite graph looks
like elsewhere: wherever a domain has both a "prepare" and a "commit" verb, assume they travel
together. .mail has createDraft → saveDraft → sendDraft as an explicit chain, which is
exactly the shape a confirmation flow needs.
One enum — category, which the docs pass counted as zero. Mail otherwise has no closed
vocabularies: a mailbox is a name, not a case.
.maps — 6 intents · 6 entities · 7 enums¶
Intents: startNavigation, stopNavigation, addNavigationWaypoints, shareETA,
stopShareETA, reportIncident
Entities: currentLocation, navigationSession, operatingHours, operatingTimeRange,
place, rating
Enums: amenity, incident, navigationPreferences, operatingStatus, priceRange,
ratingDescriptor, transportType
The most instructive domain in the whole list, because of what it lacks.
.maps has navigationSession as an entity and addNavigationWaypoints as an intent. It has
currentLocation. It has a rich place model — operatingHours, operatingTimeRange, rating,
priceRange, amenity — clearly intended for "is it open?" and "is it any good?".
What it does not have is any schema for querying progress. There is no "how far have I gone", no "how much elevation is left", no "what's my ETA to the next waypoint". Navigation can be started, stopped and amended. It cannot be interrogated.
That gap is the direct structural cause of forum thread 837249: a hiking/cycling app wanting Siri
to answer, mid-activity, "How much farther to the destination?" and "How far have I already
gone?". The developer built a custom AppEntity and an EntityQuery; Siri executed the query,
ignored the data, and read the visible on-screen text instead — "Distance: 0.21 mi", "Remaining
Distance: 0.14 mi" — from the current tab only, unable to reach data on other tabs. Zero
replies. Apple never answered.
That developer had no schema to adopt even if they had wanted to: there is no fitness domain
(§6), and .maps covers starting and stopping navigation but not asking about it. §5.7 of the
research pass and §1 above explain the second half of the failure — those are descriptive
on-screen questions, which take the screenshot path and never consult entities. The entity plumbing
was built for a request class that does not use it.
PlaceDescriptor — from GeoToolbox, not App Intents — is the system's canonical "a place"
currency type. It shows up as a .calendar.event location union member (§5.1) and as the payload
you export to Maps via ValueRepresentation (§13.1). If you model places at all, model them as
PlaceDescriptor.
.messages — 5 intents · 4 entities · 5 enums¶
Intents: draftMessage, sendMessage, editSentMessage, unsendMessage,
setMessageReadStatus
Entities: conversation, customAttachment, message, messagePerson
Enums: conversationAttribute, customReaction, messageAttribute, messageEffect,
messageType
The domain with the only verified co-requisite pair: adopting sendMessage without
draftMessage is a build error (§3.3).
customAttachment is the interesting entity. It is what makes "send this to \<contact>" able
to carry a payload rather than just text. If you are building a messaging app that handles anything
richer than strings, that is the noun to look at first.
messagePerson is a distinct entity from .phone's phonePerson and from the system's
IntentPerson value type — three different person representations across the surface. 🔴 GAP:
we do not have the relationship between them documented. What would resolve it: the per-schema doc
pages. Safe default: where a schema names a person type, use the one the schema names, and use
IntentPerson for cross-app value transfer (§13.1) where no schema dictates otherwise.
An entity-level example from Apple's own code, notable because it is where indexing and schema adoption meet in three lines:
// ✅ VERIFIED (Apple code sample, WWDC26 240 @ 7:59)
// Contributing message content to Apple Intelligence
@AppEntity(schema: .messages.message)
struct MessageEntity: IndexedEntity {
// The text content of the message
@Property(indexingKey: \.textContent)
var body: AttributedString?
}
.notes — 2 intents · 3 entities · 0 enums¶
Intents: createNote, updateNote
Entities: account, folder, note
✅ VERIFIED (docs) — "Make your note-taking app's actions available to Apple Intelligence and Siri by adopting schemas for common note actions."
No deleteNote. No search. Two verbs. Note that .reminders has a delete verb and .calendar
has one, so the omission here is a choice rather than a pattern.
.notes matters disproportionately because of a DTS answer in thread 829586: asked what to do
about plain-text documents that fit no domain, Apple's DTS engineer recommended
@AppIntent(schema: .notes.createNote). That is an Apple-staff answer and it is worth taking
seriously as a precedent — the schema whose shape fits your content may be a legitimate
adoption target even if the schema's name is not what you would have called your app. A
document-capture app is not a note-taking app, but "create a thing containing text" is the same
contract.
That precedent has limits. It works because createNote genuinely describes the action. It would
not license adopting .notes.createNote for something that is not textual content creation.
.phone — 1 intent · 1 entity · 1 enum¶
Intents: startCall
Entities: phonePerson
Enums: audioVisualMode
✅ VERIFIED (docs) — "Make your phone app's actions available to Apple Intelligence and Siri by adopting schemas for calling actions."
The smallest domain. One verb, and the enum is presumably audio-vs-video. If you ship a VoIP app, this is a single afternoon and it is the highest-leverage single schema in the list relative to its size: "call \<person>" is a request people already make constantly, and being the app that answers it is worth more than most multi-schema adoptions.
.photos — 28 intents · 3 entities · 4 enums¶
The largest intent surface of any domain, by more than double.
Intents: addAssetsToAlbum, cleanupPhoto, copyEdits, createAlbum, createAssets, crop,
deleteAlbum, deleteAssets, duplicateAssets, editAsset, openAlbum, openAsset,
pasteEdits, postToSharedAlbum, removeAssetsFromAlbum, setDepth, setExposure, setFilter,
setRotation, setSaturation, setWarmth, straighten, toggleDepth, toggleSuggestedEdits,
updateAlbum, updateAsset, updateRecognizedPerson, search (deprecated)
Entities: album, asset, recognizedPerson
Enums: albumType, assetType, filterType, rotationDirection
What 28 intents over 3 entities tells you: schemas can be extremely fine-grained inside a blessed
domain. setExposure, setWarmth, setSaturation, straighten, crop, setRotation,
setDepth — these are individual slider adjustments, each with its own schema. There is no generic
applyEdit(parameter:value:); there are seven separate verbs.
That granularity is what makes "make this a bit warmer" work without Siri having to know your
app's parameter names. It is also a warning about adoption cost: full .photos coverage is a
substantial project, and unlike .messages there is no evidence that these verbs come in
co-requisite sets, so partial adoption looks viable here — crop and straighten without
setDepth is a coherent app.
.photos.asset is the schema with the verified required-property list and the id: Int
constraint (§3.2). Plan for it.
.photos.search is deprecated — see §7.
.reminders — 8 intents · 5 entities · 2 enums¶
Intents: createList, createReminder, createSection, deleteReminders, updateGroup,
updateList, updateReminder, updateSection
Entities: group, list, locationTrigger, reminder, section
Enums: listType, locationTriggerEvent
A four-level containment model — group contains list contains section contains reminder —
which is more structure than most task apps have. If your app is flatter, §3.6's rule applies:
optional properties you do not use stay unset.
locationTrigger + locationTriggerEvent is the entity worth noticing. Geofenced reminders
are modelled as first-class schema content, which means "remind me to do X when I get to Y" can
route to your app. Very few app categories get that kind of proactive hook.
⚠️ Every update* verb in this domain — and there are four of them — is exposed to the
valueState bug class in §14.1. .reminders is the domain where "remove the due date" is a normal
thing to say, and where getting it wrong is silent.
.system — "System and in-app search" — 3 intents · 0 entities · 0 enums¶
Intents: open, searchInApp, search (deprecated — renamed; see §8)
✅ VERIFIED (docs) — the
.systemdomain provides "a structured representation for common search actions and content" applicable to any app category that handles searching or opening content.
✅ SDK-verified (AppIntents-27.0-macos.swiftinterface:13762-13797) — and the doc-page lag
noted in §8.2 is now resolved from the SDK side: the interface declares all three, with open and
searchInApp on an anyAppleOS 27.0 extension and the deprecated search on the iOS 18-era one.
The docs pass counted two because the domain page had not caught up.
This is the domain that matters most to readers who found no other domain, and it is the smallest primary domain in the list. Two live verbs, both category-agnostic, both worth adopting by nearly every app in existence — plus one deprecated name.
.system.open is the cheapest high-value schema in the entire enumeration and it fixes a
concrete, visible defect. Session 344's diagnostic story:
✅ VERIFIED (transcript, WWDC26 344) — "tapping the event from my conversation with Siri opens CometCal, but it just lands on the main screen. It doesn't navigate to the event like I would expect. Siri doesn't know how to open a specific event in the app yet."
The fix is one small intent:
// 🟡 RECONSTRUCTED from WWDC26 344 narration — session 344 published no code block.
// Verified from narration: the parameter is named `target`; the schema is `.system.open`.
import AppIntents
@AppIntent(schema: .system.open)
struct OpenEventIntent {
var target: EventEntity
@Dependency var navigation: NavigationManager
@MainActor
func perform() async throws -> some IntentResult {
navigation.navigate(to: target)
return .result()
}
}
✅ VERIFIED (transcript, WWDC26 344) — "here's an
OpenEventIntent. It's a small intent that conforms to thesystem.openschema. It takes anEventEntityas itstargetand tells theNavigationManagerto navigate to that event. The system calls this whenever someone taps an event result in Spotlight or Siri, or asks Siri to open one."
Three trigger surfaces for one small type: a Spotlight result tap, a Siri result tap, and an explicit "open X" request. Without it, every one of those dead-ends on your root screen — and the user experiences that as your app being broken, not as a missing integration.
Put .system.open at the top of your adoption checklist regardless of what else you adopt. One
version note from the SDK pass: the .system.open dot-syntax accessor is anyAppleOS 27.0 in the
interface — the 26.5 interface's .system domain has only search — so writing it takes the
Xcode 27 SDK even though the underlying open-intent machinery is older
(✅ AppIntents-27.0-macos.swiftinterface:13783-13797).
.system.search is the other half, and it is where the story gets interesting enough to
deserve its own section. It is deprecated as a name, not as a capability — it was renamed to
.system.searchInApp. See §7 for the deprecation set and §8 for the code.
5.2 Single-purpose domains (2)¶
Two domains, one intent each, and each one wired to exactly one system surface. Neither is what its name suggests.
.assistant — 1 intent · ⚠️ JAPAN ONLY¶
Intents: activate
✅ VERIFIED (docs) — this schema "registers your app as a side button action on iPhone. This schema is available only in Japan." The related guide Apple links is "Launching voice-based conversational app from the side button of iPhone."
Call this one out loudly because the name is a trap. .assistant invites the assumption that
it is the general-purpose "make my app an assistant" hook — the thing every developer building a
conversational app goes looking for. It is not. It is a regional side-button registration with
exactly one action, available only in Japan.
If you are building a voice-based conversational app outside Japan and you were hoping this was
your route in: it is not, and there is no equivalent. Your options are §8's .system.searchInApp
and App Shortcuts.
.visualIntelligence — 1 intent¶
Intents: semanticContentSearch
✅ VERIFIED (docs) — the domain "connects your app to the camera control", letting users "point the camera at relevant content" and get results from your app.
Adoption is a three-part requirement, not just a macro:
- Apply the
semanticContentSearchschema. - Implement an
IntentValueQuerythat receives Visual Intelligence types. - Match captured camera/screenshot content to your app's entities and return results.
✅ VERIFIED (docs).
Step 2 is the notable one. IntentValueQuery is the only place in the entire schema system where
your app receives system-captured visual content as input. Everywhere else, the system hands
you identifiers or strings; here it hands you what the camera saw.
This is the closest existing analogue to what thread 838329's author wanted — a general "here is
some visual content, tell me what it is in your app" pipe — but it is scoped to the camera
control surface, not to arbitrary on-screen content. If your product need is "understand what is
on the user's screen", .visualIntelligence does not reach it.
🔴 GAP — the Visual Intelligence input types. The docs prose says the IntentValueQuery
"receives Visual Intelligence types" without naming them. We do not have the type list. What
would resolve it: the .visualIntelligence domain page's symbol table, or the Visual Intelligence
framework documentation. Safe default: write the query against whatever type Xcode's schema
snippet scaffolds; do not guess the type name from this guide.
5.3 Shortcuts-specific domains (8)¶
Per the index page's own grouping, these eight are Shortcuts-specific rather than Apple Intelligence / Siri tier. Read §4's caveat before planning around them.
Structurally they are the most interesting part of the map, because they are where Apple modelled productivity software — and the three iWork-shaped domains are near-isomorphic in a way that tells you how Apple thinks about document apps.
.books — 9 intents · 3 entities · 10 enums¶
Intents: navigatePage, openBook, updateCharacterSpacing, updateFontSize,
updateLineSpacing, updateSettings, updateWordSpacing, playAudiobook (deprecated),
search (deprecated)
Entities: audiobook, book, settings
Enums: contentType, font, fontSize, navigationDirection, pageNavigationSetting,
relativeCharacterSpacingChange, relativeFontChange, relativeLineSpacingChange,
relativeWordSpacingChange, theme
Ten enums for nine intents — the largest enum set of any domain in the map (✅ SDK-verified,
AppIntents-27.0-macos.swiftinterface:13522-13576; the docs pass counted twelve, but its own name
list — reproduced above — always had ten, and ten is what the interface declares). Look at
the names: relativeFontChange, relativeLineSpacingChange, relativeWordSpacingChange,
relativeCharacterSpacingChange. Four separate closed vocabularies for "a bit bigger" / "a bit
smaller". That is what it takes to make "make the text a little larger" work reliably without
Siri needing to know your app's point sizes.
Two deprecations here, and the SDK settles what replaced them. playAudiobook's deprecation
message in the interface is, verbatim, "Use .audio.playAudio instead)" — ✅ SDK-verified,
upgrading what this guide previously carried as a 🟡 inference from the entity overlap with
.audio.audiobook. search is part of the generic-search deprecation set (§7).
.browser — 13 intents · 5 entities · 1 enum¶
Intents: bookmarkTab, bookmarkURL, clearHistory, closeTabs, closeWindows, createTab,
createWindow, deleteBookmarks, findOnPage, openBookmark, openURLInTab, switchTab,
search (deprecated)
Entities: bookmark, readingListItem, tab, tabGroup, window
Enums: clearHistoryTimeFrame
A complete window/tab/bookmark model. Note that findOnPage survived while search was
deprecated — the pattern in §7 holds: domain-specific search verbs live; generic search verbs
died.
.journal — 5 intents · 1 entity · 0 enums¶
Intents: createAudioEntry, createEntry, deleteEntry, updateEntry, search
(deprecated)
Entities: entry
⚠️ The naming mismatch again: the symbol is AppSchema.JournalIntent (singular "Journal"), the
domain page is titled "Journaling" and lives at app-schema-domain-journaling, and the dot-syntax
is .journal. ✅ VERIFIED (docs).
createAudioEntry as a distinct verb from createEntry is worth noting — a voice-first creation
path modelled explicitly.
.presentation — 15 intents · 3 entities · 0 enums¶
Intents: addAudioToSlide, addCommentToSlide, addImageToSlide, addTextBoxToSlide,
addVideoToSlide, addWebVideoToSlide, create, createSlide, deleteSlide, open,
openSlide, setSlideTitle, startPlayback, stopPlayback, update
Entities: document, slide, template
✅ SDK-verified (AppIntents-27.0-macos.swiftinterface:16457-16536). The docs pass counted
fourteen: addVideoToSlide — underlying name AddVideoToPresentationSlideIntent — is declared in
the interface (:16505), and was already present in the 26.5 SDK
(AppIntents-26.5-macos.swiftinterface:5851).
.reader — 9 intents · 2 entities · 1 enum¶
Intents: deletePages, enhanceDocuments, insertPages, openDocument, openPage,
resizeDocuments, rotateDocuments, rotatePages, searchDocuments
Entities: document, page
Enums: documentKind
.reader keeps a live searchDocuments while .browser, .books, .journal and .system
all had their generic search deprecated. That is the cleanest single piece of evidence for the
§7 thesis: the surviving search verbs are the domain-specific ones, and searchDocuments is
specific — it searches documents, a modelled noun, not "stuff".
enhanceDocuments and resizeDocuments mark this as a scanning/PDF domain rather than a
general reading domain.
.spreadsheet — 14 intents · 3 entities · 0 enums¶
Intents: addAudioToSheet, addCommentToSheet, addImageToSheet, addTextBoxToSheet,
addVideoToSheet, addWebVideoToSheet, create, createSheet, delete, deleteSheet, open,
openSheet, update, updateSheet
Entities: document, sheet, template
Note what is absent: there is no cell, no formula, no range, no chart. A spreadsheet schema with no concept of a cell tells you exactly what tier it is aimed at — document-level automation in Shortcuts, not "Siri, sum column B."
.whiteboard — 7 intents · 2 entities · 2 enums¶
Intents: createBoard, createItem, deleteBoard, deleteItem, openBoard, updateBoard,
updateItem
Entities: board, item
Enums: color, itemType
The most abstract domain in the list — item with an itemType enum is a deliberately open
container. If your app is a freeform canvas of any kind (mind maps, diagrams, sticky notes,
moodboards), this is the closest fit in the enumeration, and its abstraction is a feature.
.wordProcessor — 9 intents · 3 entities · 0 enums¶
Intents: addAudioToPage, addImageToPage, addTextBoxToPage, addVideoToPage,
addWebVideoToPage, create, createPage, open, openPage
Entities: document, page, template
The iWork isomorphism¶
The three productivity domains rhyme, deliberately:
.presentation |
.spreadsheet |
.wordProcessor |
|
|---|---|---|---|
| Container entity | document |
document |
document |
| Unit entity | slide |
sheet |
page |
| Template entity | template |
template |
template |
| Insertion verbs | add{Audio,Comment,Image,TextBox,Video,WebVideo}ToSlide |
add{Audio,Comment,Image,TextBox,Video,WebVideo}ToSheet |
add{Audio,Image,TextBox,Video,WebVideo}ToPage |
| Lifecycle | create/open/update + create<Unit>/open<Unit>/delete<Unit> |
same | create/open + createPage/openPage |
✅ VERIFIED (docs), updated against the SDK census — every cell above matches the 27.0 interface.
Practical advice: if you are building an iWork-class app, adopt all three in parallel. The
shapes are the same, your adapter code will be near-identical, and the marginal cost of the second
and third domains is a fraction of the first. Note the small asymmetries — only .presentation
and .spreadsheet have comment verbs; only .presentation has playback verbs and a unit-title
verb (setSlideTitle); .wordProcessor has no update or delete verbs at all. Do not assume
symmetry you have not checked. (An earlier revision of this guide reported .presentation lacking
a plain addVideoToSlide; the SDK interface has it — see the .presentation entry above.)
5.4 The whole surface, counted¶
| Tier | Domain | Intents | Entities | Enums |
|---|---|---|---|---|
| Primary | audio |
7 | 18 | 6 |
| Primary | calendar |
3 | 3 | 4 |
| Primary | camera |
5 | 0 | 3 |
| Primary | clock |
14 | 3 | 3 |
| Primary | files |
5 | 1 | 0 |
| Primary | mail |
12 | 5 | 1 |
| Primary | maps |
6 | 6 | 7 |
| Primary | messages |
5 | 4 | 5 |
| Primary | notes |
2 | 3 | 0 |
| Primary | phone |
1 | 1 | 1 |
| Primary | photos |
28 | 3 | 4 |
| Primary | reminders |
8 | 5 | 2 |
| Primary | system |
3 | 0 | 0 |
| Single-purpose | assistant |
1 | 0 | 0 |
| Single-purpose | visualIntelligence |
1 | 0 | 0 |
| Shortcuts | books |
9 | 3 | 10 |
| Shortcuts | browser |
13 | 5 | 1 |
| Shortcuts | journal |
5 | 1 | 0 |
| Shortcuts | presentation |
15 | 3 | 0 |
| Shortcuts | reader |
9 | 2 | 1 |
| Shortcuts | spreadsheet |
14 | 3 | 0 |
| Shortcuts | whiteboard |
7 | 2 | 2 |
| Shortcuts | wordProcessor |
9 | 3 | 0 |
| Total | 182 | 74 | 50 |
✅ SDK-verified for every row (AppIntents-27.0-macos.swiftinterface, AppSchema namespace,
censused 2026-07-29): the table counts the leaf schema accessors the 27.0 beta interface actually
declares, which supersedes the doc-page census this guide originally carried. The two censuses
agree for eighteen domains; the SDK corrects five (clock +4/+1/+1, mail +1 enum, books
−2 enums, presentation +1 intent, system +1 intent). Counts include deprecated schemas
(search ×5, playAudiobook), exclude container/protocol symbols (AppSchema.MailIntent and
friends), and are macOS-surface counts — an iOS interface could conceivably differ.
Tier subtotals: primary domains carry 99 intents, 52 entities, 36 enums. Shortcuts-tier domains carry 81 intents, 22 entities, 14 enums. Single-purpose carries 2 intents.
That split is worth a moment. Nearly half the intent surface — 81 of 182 — is in the tier that
the taxonomy places outside Siri. If you counted "182 intents" and concluded the schema system
was broad, the number that actually governs un-app-qualified Siri routing is 99, spread across
thirteen domains, of which one (photos) is 28.
6. The absences — what has no domain at all¶
If you read one section of this guide, read this one. For most apps this is the answer.
Here is the complete list of app categories with no schema domain, primary or otherwise:
- Fitness and workouts — no run, ride, session, workout, rep, set, split, pace or heart-rate
schema. The
.mapsnavigation entities are the nearest thing and they cannot be queried (§5.1). - Health — no symptom, medication, dose, measurement, appointment or condition schema.
- Finance and banking — no account, transaction, transfer, balance, budget or invoice schema.
- Shopping and commerce — no product, cart, order, delivery, return or wishlist schema.
- Travel and booking — no flight, hotel, reservation, itinerary or boarding-pass schema.
- Food ordering and recipes — no dish, menu, order, ingredient or recipe schema.
- Ride-hailing and transport — no ride, trip, driver, fare or transit schema. (Navigation exists; being driven does not.)
- Social feeds — no post, feed, follow, like, comment or profile schema.
- Education and learning — no course, lesson, assignment, deck, card or quiz schema.
- Developer tools — no repository, build, issue, pull request or deployment schema.
- Games — nothing at all.
- Smart home — not absent so much as elsewhere: that surface lives in HomeKit, not App Intents schemas.
✅ VERIFIED (docs) as an absence: this list is derived by taking the complete 23-domain
enumeration from the index page and naming what is not in it. Absence claims are weaker than
presence claims — a domain could exist and be undocumented — but the index page is precisely the
page whose job is to enumerate domains, so its silence is meaningful. And as of 2026-07-29 the
absence is ✅ SDK-corroborated: the 27.0 beta interface's AppSchema namespace declares
exactly the 23 domains in §5.4 and no others — phrased carefully, none of the categories above is
present in the macOS 27.0 beta SDK interface.
What this means concretely¶
If your app is in any of those categories:
- You have no primary schema domain to adopt. There is therefore no route to un-app-qualified Siri actionability through the schema system for your app's core concepts.
- Your custom
AppEntitytypes are still DISCOVERABLE. Index them (IndexedEntity+CSSearchableIndex.indexAppEntities(_:), guide 04) and Siri can find them, name them and surface them in Spotlight. That is real value and it is not gated on schemas. - They are not ACTIONABLE. Siri will not execute an action against them, and will not reliably move them across an app boundary. That is Apple's documented architecture (§2), not a defect.
.system.searchInAppis your one reachable Siri hook (§8). It works regardless of domain adoption and regardless of indexing. It navigates the user into your app's own search UI rather than answering in Siri's voice — but it is a supported, un-app-qualified Siri entry point, and for an uncovered category it is the only one.- App Shortcuts remain fully open (§1A), at the cost of the user naming your app.
The two-sided honesty about this¶
The optimistic side. Look again at §5.1's .notes entry: Apple's own DTS engineer recommended
.notes.createNote for plain-text documents in an app that is not a note-taking app. Schemas
describe shapes of actions, not app categories. Before concluding you have no domain, check
whether one of the 13 primary domains describes a shape your app performs:
| Does your app… | Look at |
|---|---|
| create textual content? | .notes.createNote / .notes.updateNote |
| play any audio at all — podcasts, ambient sound, radio, lessons? | .audio (18 entities; ambientSound, newsBrief and radioShow are broader than they sound) |
| show places, or navigate? | .maps |
| produce or consume files? | .files (and this is the on-screen hand-off route) |
| set timers or alarms? | .clock (14 verbs including stopwatches; a workout interval timer is a timer) |
| let the user call someone? | .phone |
| capture from the camera? | .camera (zero entities — very cheap) |
| open a specific item? | .system.open — adopt this regardless |
| have any search UI at all? | .system.searchInApp — adopt this regardless |
A fitness app has timers, plays audio, shows places, and captures photos. None of those make it a fitness domain, but each is a genuine Siri capability it can have today.
The pessimistic side, which is also true. None of that gives you the request the developer in thread 837249 actually wanted: "How much farther to the destination?", asked hands-free, mid-ride, without naming the app. That request needs a schema for querying activity progress, and there is not one. That developer's thread has zero replies and no first-party answer, and thread 829586 — with 14 replies and about a thousand views — records FB23018652 being characterised by DTS as an enhancement request, not a bug.
So the honest summary for an uncovered category is: you can be found, you can be opened, you can be searched, and you can be invoked by name. You cannot be acted on generically. Design your Siri story around the first four.
One workaround, reported and untested¶
Thread 829586 records a proposed route that nobody in the thread confirmed working:
Community-proposed, UNTESTED (thread 829586): expose custom entities via discoverability (indexing), build an in-app agent, and expose intents to Siri that message that agent, conforming to the Messages app schema domain.
We are recording it because it is in circulation, not because we recommend it. It routes a
general-purpose request through a schema whose contract is about sending messages between people.
🔴 GAP: whether Siri actually routes to it, and whether it survives review, is unverified.
Safe default: do not build on this. Use .system.searchInApp plus App Shortcuts, and file a
feedback referencing FB23018652 so the enhancement request accumulates weight.
7. Deprecations, and where generic search went¶
Five schemas are marked deprecated across the enumeration. Four of them are the same schema in different domains.
| Deprecated schema | Domain | Tier | Replacement |
|---|---|---|---|
.system.search |
system |
Primary | Renamed .system.searchInApp (§8) |
.browser.search |
browser |
Shortcuts | Spotlight / IndexedEntity; .system.searchInApp |
.books.search |
books |
Shortcuts | Spotlight / IndexedEntity; .system.searchInApp |
.journal.search |
journal |
Shortcuts | Spotlight / IndexedEntity; .system.searchInApp |
.books.playAudiobook |
books |
Shortcuts | .audio.playAudio — ✅ SDK deprecation message |
✅ VERIFIED (docs) for the deprecation marks themselves, and — as of 2026-08-23, against the
beta 5 recapture — ✅ SDK-verified for the whole replacement column: the 27.0 interface
attaches a message to each deprecated accessor. .system.search says "Use .system.searchInApp
instead" (AppIntents-27.0-macos.swiftinterface:13776), and beta 5 re-pointed the photos,
browser, journal and books search schemas at the terminal replacement directly — all four
now say "Use .system.searchInApp instead" (AppIntents-27.0-macos.swiftinterface:11518,
:12421, :13332, :13412), collapsing the two-hop chain (search → .system.search →
.system.searchInApp) the beta 4 capture documented; only the legacy AssistantSchemas search
accessors still point, with a straight face, at the deprecated .system.search (:14307 et al.).
.books.playAudiobook says "Use .audio.playAudio instead"
(AppIntents-27.0-macos.swiftinterface:13418), confirming what this guide previously carried as a
🟡 inference. (Beta 5 also modernized the attribute form to @available(anyAppleOS, deprecated:
27.0, …) and dropped the stray ) the beta 4 messages carried.)
Note also .photos.search, marked deprecated in the .photos enumeration (§5.1) — a sixth
instance of the same pattern.
The thesis: generic search became infrastructure¶
The deprecation set is not random. Every deprecated search is a generic search — "search my
app for a string". Every surviving search verb is domain-specific: .reader.searchDocuments
lives, .browser.findOnPage lives.
Two things absorbed generic search:
1. Spotlight and IndexedEntity. Session 240's argument is that string-matching was never what
people wanted anyway:
✅ VERIFIED (transcript, WWDC26 240) — "When someone says something like: 'The best windsurfing in Carmel', they're not looking for an exact text match. They're expressing meaning. To support that experience, Siri needs more than string matching. It needs semantic search. And that's exactly what
IndexedEntityenables."
And:
✅ VERIFIED (transcript, WWDC26 240) — "'Show the messages with Flare about movies' — that's not a string match. Siri can find messages that reference movie titles because it's performing a semantic query over UnicornChat's indexed messages."
A per-domain search intent cannot deliver that. It hands you a string; you do a LIKE '%…%'. The
semantic index does the meaning part before your app is involved, and returns entities.
2. .system.searchInApp, for the case where you want Siri's query string re-run inside your
search UI (§8).
What to do if you have adopted a deprecated schema¶
.system.search→ rename the schema reference. This is the cheapest possible migration because it is a rename, not a replacement with new semantics (§8). Yourcriteria: StringSearchCriteriaparameter and yourperform()body are unchanged..browser.search/.books.search/.journal.search→ two moves, and you probably want both: adopt.system.searchInAppso the un-app-qualified Siri path still lands in your search UI, and adoptIndexedEntity+indexAppEntitiesso the content becomes findable semantically without an intent at all. Guide 04 covers the second..books.playAudiobook→ 🟡 look at.audio.playAudiowith.audio.audiobookas the entity. Verify against the.audiodomain page before committing; we have not confirmed the parameter mapping.
⚠️ Deprecated does not mean removed. These schemas are still in the enumeration and still counted in §5.4 — and the 27.0 beta interface still declares every one of them, so they compile with warnings today (✅ SDK, checked 2026-07-29). Nothing in our sources says when — or whether — they stop compiling. 🔴 GAP: no removal timeline is published in any source we read. Safe default: migrate at your convenience, but do not assume a deprecated schema will keep working across two OS majors.
8. .system.searchInApp — the escape hatch¶
This is the single most useful fact in this guide for anyone whose category is in §6.
8.1 What it is, in Apple's words¶
✅ VERIFIED (transcript, WWDC26 343 @ ~15:27) — "To do this, I'll adopt the system
.searchInAppschema. The.systemsearch schema introduced in iOS 17 is now named.system.searchInApp. It is part of the System App Schema domain, and it lets people search in your app with Siri, no matter which other domains you adopt, and even if you don't index your entities."
Three separate facts in one sentence, and all three matter:
- The exact spelling is
.system.searchInApp, used as@AppIntent(schema: .system.searchInApp). - It is a RENAME, not a new schema. The iOS 17
.systemsearch schema "is now named.system.searchInApp." Same schema, new name — not a replacement with new semantics. - It works regardless of domain adoption or entity indexing. No prerequisite. This is the property that makes it the escape hatch.
8.2 Why we believe the rename claim¶
This one has unusually good evidence for a 2026 API name, so it is worth showing the chain:
- Two independent renderings on the same page. The sentence above is transcript prose. Apple
also publishes a separate code-sample block on session 343's page, and it contains
@AppIntent(schema: .system.searchInApp)at timestamp 14:49. Those are two different artifacts generated from the same session, and they agree. - Corroboration from an unrelated type's age. The parameter type is
StringSearchCriteria, and that type's documentation page gives its availability as iOS 17.2+, iPadOS 17.2+, Mac Catalyst 17.2+, macOS 14.2+, tvOS 17.2+, watchOS 10.2+ (visionOS listed with an undefined version). ✅ VERIFIED (docs), fetched from/documentation/appintents/stringsearchcriteria. The type is old. IfsearchInAppwere a genuinely new 2026 schema you would expect a new criteria type. An iOS 17.2 parameter type on a schema Apple describes as "the iOS 17 search schema, renamed" is exactly what a rename looks like. - The doc-page lag. The
.systemdomain page still lists onlyopenandsearch(deprecated), and does not listsearchInApp. That is the one piece of counter-evidence, and the most likely explanation is documentation lag: the page title is "System and in-app search", which corroborates that in-app search belongs there.
✅ SDK-verified — the gap this box used to carry is closed. The SDK interface dump this guide
asked for now exists, and it settles the spelling outright. The 27.0 interface declares, on
extension AppSchema.SystemIntent at @available(anyAppleOS 27.0, *), the accessor
var searchInApp — underlying intent name SystemSearchInAppIntent — alongside var open
(AppIntents-27.0-macos.swiftinterface:13783-13797). The deprecated var search — underlying
name ShowInAppSearchResultsIntent — carries the SDK's own message: "Use .system.searchInApp
instead" (:13774-13782). That is the rename, stated by the compiler. The 26.5 interface has
only search (AppIntents-26.5-macos.swiftinterface:5574-5582), so the searchInApp name
requires the Xcode 27 SDK — while the underlying ShowInAppSearchResultsIntent protocol is
macOS 14.2 / iOS 17.2 (AppIntents-27.0-macos.swiftinterface:809-810), which squares with "the
iOS 17 search schema, renamed" exactly. The fallback advice stands: on an older SDK, write the
deprecated .system.search; it is the same schema.
8.3 The complete code¶
Apple's own sample first, verbatim:
// ✅ VERIFIED (Apple code sample, WWDC26 343 @ 14:49)
// Intent that re-runs the Siri search in app
@AppIntent(schema: .system.searchInApp)
struct SearchAudioLibraryIntent {
var criteria: StringSearchCriteria
func perform() async throws -> some IntentResult {
// Perform in-app search with Siri search string
navigation.searchText = criteria.term
navigation.selectedTab = .library
return .result()
}
}
Note Apple's own header comment: "Intent that re-runs the Siri search in app." That is the mental model in six words. Siri hands you its own query string and gets out of the way.
Now a complete, compilable version for an app that has no schema domain — the hiking-app case from
thread 837249. The important dependency detail is to construct one NavigationModel, store that
object as the app's state, and register that exact object with add(dependency:); @Dependency
then resolves the same instance instead of a separately constructed model.1
import AppIntents
import SwiftUI
import Observation
// MARK: - App-side navigation state
//
// Nothing App-Intents-specific here. This is just the object that owns
// "which tab is showing, and what is in the search field."
@MainActor
@Observable
final class NavigationModel {
enum Tab: Hashable { case activity, routes, history, settings }
var selectedTab: Tab = .activity
var searchText: String = ""
func showSearch(_ term: String) {
searchText = term
selectedTab = .routes
}
}
// MARK: - Dependency registration
//
// @Dependency is how App Intents injects shared objects into intents and
// queries — "instead of creating new instances, it provides the same object
// I register once." (✅ VERIFIED, transcript, WWDC26 344)
@main
@MainActor
struct TrailLogApp: App {
@State private var navigation: NavigationModel
init() {
let navigation = NavigationModel()
_navigation = State(initialValue: navigation)
// Register the same instance supplied to RootView below.
AppDependencyManager.shared.add(dependency: navigation)
}
var body: some Scene {
WindowGroup {
RootView()
.environment(navigation)
}
}
}
// MARK: - The escape hatch itself
@AppIntent(schema: .system.searchInApp)
struct SearchTrailLogIntent {
// Required by the schema. StringSearchCriteria is iOS 17.2+ / macOS 14.2+.
var criteria: StringSearchCriteria
@Dependency private var navigation: NavigationModel
@MainActor
func perform() async throws -> some IntentResult {
navigation.showSearch(criteria.term)
return .result()
}
}
Three implementation notes on that listing:
criteria.term— ✅ VERIFIED (docs): "The string value used for matching items in the application." — and now ✅ SDK-verified (AppIntents-27.0-macos.swiftinterface:792-808):public var term: String,init(term:), conformancesSearchCriteria,Sendable,EquatableandHashable, availabilitymacOS 14.2 / iOS 17.2 / watchOS 10.2 / tvOS 17.2— the exact floor the docs page gave.@MainActoronperform()because it mutates UI state. Session 240 makes exactly this point about the Xcode-generateddraftMessagestub: it "needs to run on the main actor" because it "mutates UI state." ✅ VERIFIED (transcript, 240).AppDependencyManager.shared.add(dependency:)is Apple's documented registration spelling. The official sample constructs one navigation model, stores it for the app, registers that same value, and retrieves it from an intent with@Dependency; session 344 independently describes the same-instance contract.1
8.4 What it does and does not buy you¶
✅ VERIFIED (transcript, WWDC26 343) — "Siri calls this intent with the same string it searched for, and the intent's perform method finds and shows those results in the app."
And the motivation, which is the answer to "why not just let Siri render results?":
✅ VERIFIED (transcript, WWDC26 343) — "Siri can display a list of entity search results. That's a nice default. But I spent a lot of time crafting the app's own search experience, and I'd love to show these results there."
So the honest ledger:
| It gives you | It does not give you |
|---|---|
| An un-app-qualified Siri entry point | An answer spoken in Siri's voice |
| Your own search UI, with your own ranking, filters and design | A result Siri can reason further about |
| Zero prerequisites — no domain adoption, no indexing | Anything hands-free (it navigates; the user must look) |
| A one-struct adoption | Any ability to act on the results |
For an uncovered category (§6), this is the whole schema-tier story. It is a navigation hook, not an intelligence feature. That is a real limitation and you should scope product expectations to it — but it is also a supported, first-party, un-app-qualified Siri entry point for an app that has no other one, and there is nothing else in the enumeration that does that.
8.5 Adopt it even if you do have a domain¶
Note the phrasing again: "no matter which other domains you adopt." This is not an
either/or. A photos app that adopts twenty .photos schemas should still adopt
.system.searchInApp, because there will always be requests the fine-grained verbs do not cover,
and landing the user in your search UI with the right string in the field is a much better failure
mode than Siri shrugging.
Pair it with .system.open (§5.1). Together they are two small structs that fix the two most
visible dead-ends in any Siri integration: "I can't search your app" and "tapping the result does
nothing."
9. Decision tree: which route is open to you¶
Run this once, before writing any code. It takes ten minutes and it will save you either three weeks or a wrong architecture.
START: What does my app's core experience DO?
│
├─ 1. Is that action in one of the 13 PRIMARY domains? (§5.1)
│ Check by SHAPE, not by app category (§6):
│ create text · play audio · navigate/show places · handle files ·
│ set timers/alarms · call people · capture from camera ·
│ send messages · manage mail · edit photos · manage reminders/calendar
│ │
│ ├─ YES → Adopt it.
│ │ • Type `<domain>_` in Xcode, read what the snippet requires (§3.4)
│ │ • Check the identifier type it forces on you (§3.2)
│ │ • Expect a co-requisite build error; adopt in pairs (§3.3)
│ │ • Add IndexedEntity + indexAppEntities so Siri can find operands (§10.1)
│ │ • Add .system.open and .system.searchInApp anyway (§8.5)
│ │ → FULL: un-app-qualified Siri, discovery AND action
│ │
│ └─ NO → continue
│
├─ 2. Is it in one of the 8 SHORTCUTS-SPECIFIC domains? (§5.3)
│ books · browser · journal · presentation · reader ·
│ spreadsheet · whiteboard · wordProcessor
│ │
│ ├─ YES → Adopt it, but read §4 first.
│ │ The taxonomy places these OUTSIDE the Siri tier.
│ │ Plan the feature as Shortcuts automation, not as a Siri answer.
│ │ Still add .system.open + .system.searchInApp for the Siri path.
│ │ → PARTIAL: Shortcuts actions; Siri reach unverified
│ │
│ └─ NO → continue
│
├─ 3. Do I connect to the camera control specifically? (§5.2)
│ └─ YES → .visualIntelligence.semanticContentSearch + IntentValueQuery
│ → NARROW: one surface, but a genuinely unique one
│
├─ 4. Am I a voice-conversational app shipping in Japan? (§5.2)
│ └─ YES → .assistant.activate — side-button registration
│ → NARROW and REGIONAL. Outside Japan: not available.
│
└─ 5. NONE OF THE ABOVE — the §6 case.
│
├─ ALWAYS: .system.searchInApp (§8) → un-app-qualified Siri entry
├─ ALWAYS: .system.open (§5.1) → results become tappable
├─ ALWAYS: IndexedEntity + indexAppEntities (guide 04) → discovery
├─ ALWAYS: AppShortcutsProvider (§1A) → voice, user names the app
├─ CONSIDER: RelevantEntities (§13.3) → situational suggestions
└─ FILE FEEDBACK referencing FB23018652 → the enhancement request
→ DISCOVERABLE + SEARCHABLE + OPENABLE + INVOCABLE-BY-NAME.
NOT generically actionable. Scope the product to that.
The four things everyone should adopt regardless of branch¶
This is the floor, and it is cheap:
| # | Adopt | Cost | What breaks without it |
|---|---|---|---|
| 1 | .system.open |
one struct | Every Siri/Spotlight result tap dead-ends on your root screen |
| 2 | .system.searchInApp |
one struct | No un-app-qualified Siri entry point at all |
| 3 | IndexedEntity + indexAppEntities |
one protocol + index calls on create/update/delete | Siri cannot find your content to name it |
| 4 | AppShortcutsProvider |
one type | No voice invocation of anything |
Numbers 1 and 2 are §5.1 and §8 of this guide. Number 3 is guide 04. Number 4 is ordinary App Intents.
The cost model, from Apple's own code-along¶
Worth quoting as a calibration point, because adoption cost is the thing people most often overestimate. Session 344 built a calendar app's entire content layer — calendar, attendee and event entities, indexed, with queries — and summarized it:
✅ VERIFIED (transcript, WWDC26 344) — "Not bad for three structs and filling out a few code snippets, right?"
and, on connecting on-screen content:
✅ VERIFIED (transcript, WWDC26 344) — "Seriously… two modifiers… that's all it takes to connect what's on screen to the app's content."
Treat that as the best case: a SwiftUI + SwiftData app, in a domain with complete schema coverage, presented by the team that built the framework. Your mileage will vary most where the schema dictates an identifier type your persistence layer does not already have (§3.2).
10. Query protocols and how entity resolution works¶
Adopting a schema makes an action executable. It does not make Siri able to find the thing to execute it on. That is entity resolution, and it runs through a query protocol. Picking the wrong one is one of the most common causes of "Siri can't find my stuff."
10.1 The five protocols¶
| Protocol | Input | Method | Use when |
|---|---|---|---|
EntityQuery |
[ID] |
entities(for:) |
Baseline. Resolve identifiers back to entities. Everything needs this. |
EntityStringQuery |
String |
entities(matching:) |
You cannot index ahead of time. You own the search logic. |
EnumerableEntityQuery |
— | allEntities() |
The system needs to offer your entities as choices |
IndexedEntityQuery |
reindex requests | reindexEntities(for:indexDescription:), reindexAllEntities(indexDescription:) |
You use IndexedEntity and must service Spotlight reindex callbacks |
IntentValueQuery |
structured system types | values(for:) |
Large / server-side / fast-changing data; Visual Intelligence input; resolving IntentPerson / AudioSearch |
✅ VERIFIED (docs) for the protocol names and the IndexedEntityQuery method signatures; ✅
VERIFIED (Apple code sample) for EntityStringQuery and IntentValueQuery shapes below; ✅
VERIFIED (transcript, 344) for EnumerableEntityQuery. As of 2026-07-29 all of them are also
✅ SDK-verified, with availability floors the docs pass could not supply
(AppIntents-27.0-macos.swiftinterface): EntityQuery macOS 13 / iOS 16 (:4200);
EntityStringQuery macOS 13 / iOS 16 (:4229); EnumerableEntityQuery macOS 14 / iOS 17
(:4233); IndexedEntityQuery macOS 27 / iOS 27 / visionOS 27 (:4251); and IntentValueQuery
at anyAppleOS 26.0 — it is not new this year (:4402). A sixth, UniqueAppEntityQuery,
exists for singleton entities and predates this release — ✅ SDK-verified at macOS 15 / iOS 18,
requirement func uniqueEntity() async throws (:2726-2729).
10.2 EntityQuery versus EnumerableEntityQuery — a clean rule¶
Session 344 gives the distinction in one line, and it is the kind of rule you remember:
✅ VERIFIED (transcript, WWDC26 344) — "
EntityQuerycovers cases where the system already knows an entity's ID. But later, when creating events, the system will need to know which calendars are available so Siri can offer them as options. For that, I'll conform toEnumerableEntityQueryand add anallEntitiesmethod."
EntityQuery = resolve by ID. EnumerableEntityQuery = offer as a choice.
If any intent parameter is a small, bounded set the user might be asked to pick from — which
calendar, which account, which mailbox, which list — that entity needs EnumerableEntityQuery.
Without it, Siri has an intent it can run and no way to enumerate the options.
// 🟡 RECONSTRUCTED from WWDC26 344 narration (no published code block).
// The facts asserted — the two protocols, the two method names, @Dependency,
// and the @MainActor propagation — are all stated in narration.
import AppIntents
@MainActor
struct CalendarEntityQuery: EntityQuery, EnumerableEntityQuery {
@Dependency var calendarManager: CalendarManager
func entities(for identifiers: [UUID]) async throws -> [CalendarEntity] {
// fetch by ID
}
func allEntities() async throws -> [CalendarEntity] {
// every calendar the user has
}
}
On the @MainActor there: session 344 notes "The CalendarManager is main-actor isolated, so I'll
also annotate the query struct as @MainActor." ✅ VERIFIED (transcript, 344). Actor
isolation propagates from your data layer into your queries; plan for it rather than fighting it.
10.3 EntityStringQuery — the "I can't index" fallback¶
// ✅ VERIFIED (Apple code sample, WWDC26 240 @ 8:36)
// An interface that locates entities using arbitrary string input
struct ContactQuery: EntityStringQuery {
func entities(matching string: String) async throws -> [ContactEntity] {
let predicate = #Predicate<Person> { person in
person.name.localizedStandardContains(string)
}
let descriptor = FetchDescriptor<Person>(predicate: predicate)
let matches = try modelContext.fetch(descriptor)
return matches.map(\.entity)
}
}
The trade-off, stated by Apple in one sentence — quote this when someone asks why they should bother indexing:
✅ VERIFIED (transcript, WWDC26 240) — "You don't get semantic understanding, but you do get full control over how you search for and match your app's entities."
10.4 When not to index — Apple's fixed formula¶
Three different sessions state the same test in near-identical words, which means it is the official rule rather than one presenter's opinion:
✅ VERIFIED (transcript, WWDC26 343) — "you might not index your entities if your content dataset is large, lives on a server, or changes too frequently to index ahead of time. For example, I decided to index all the app's playlists, but not songs."
Session 240 uses the same phrasing — "Your dataset might be large, lives on a server, or changes too frequently to index ahead of time" — and session 345 repeats it again. ✅ VERIFIED (transcript, 240 and 345).
The worked judgement is the useful part: index playlists, not songs. Playlists are few, stable, and user-named — exactly what semantic matching is good at. Songs are many, churn with the catalog, and are better served by a structured query. That is a mixed strategy inside one app, and it is the right shape for most media apps.
10.5 IntentValueQuery — structured search, and the only multi-type query¶
✅ VERIFIED (transcript, WWDC26 343) — "
IntentValueQueryis suitable if you don't index all your entities ahead of time. This is very similar toEntityQuery. The key differences are that your app receives a structured search input from the system, and you can return more than one entity type."
// ✅ VERIFIED (Apple code sample, WWDC26 343 @ 13:38)
// Structured search of songs and playlists
struct AudioIntentValueQuery: IntentValueQuery {
// AudioSearch, IntentPerson, and other system types may be supported as input
func values(for input: AudioSearch) async throws -> [AudioEntity] {
switch input.criteria {
case .searchQuery(let query):
return try await searchResults(for: query)
case .unspecified:
return try await likedSongResults()
// ... also a .url case
}
}
}
What each criteria case means, from the narration — this is where the design intent lives:
| Case | Meaning | Example utterance |
|---|---|---|
.searchQuery(String) |
"contains the relevant part of what the person said" — Siri has already stripped the carrier phrase | "play something by Glow" |
.unspecified |
no specific target named; do something sensible | "'Play CosmoTunes' which isn't specific about what I want to play. In that case, the app jumps straight into playing songs I've previously liked." |
.url |
"for when someone references a link from your app" | "Play that playlist Glow sent me." |
✅ VERIFIED (transcript, WWDC26 343) for all three descriptions.
.unspecified is the case most developers will forget, and it is the one that handles the
most common utterance of all — the bare app name. Handle it with a sensible default rather than
returning empty.
⚠️ Do not assume the input is scalar. Session 343's input is a scalar AudioSearch. Session
240's is an array:
// ✅ VERIFIED (Apple code sample, WWDC26 240 @ 19:21)
// Working across apps - IntentValueQuery
struct ContactEntityQuery: IntentValueQuery {
func values(for input: [IntentPerson]) async throws -> [ContactEntity] {
let names = input.map(\.displayName)
let descriptor = FetchDescriptor<Contact>()
let contacts = try model.mainContext.fetch(descriptor)
let matches = contacts.filter { contact in
names.contains(where: { name in
contact.name.localizedStandardContains(name)
})
}
return matches.map(\.entity)
}
}
values(for:) is generic over the input type and that type may itself be a collection — ✅
SDK-verified: the protocol's requirement is func values(for input: Self.Input) async throws
-> Self.Result over an Input : _IntentValue associated type
(AppIntents-27.0-macos.swiftinterface:4401-4412), so any conforming input type, scalar or
collection, is admissible.
⚠️ One doc-vs-SDK conflict inside session 240's sample. Apple's published code maps
input.map(\.displayName) — but the macOS 27.0 interface's IntentPerson has no displayName
property. It has var name: IntentPerson.Name, an enum whose cases are .displayName(String),
.components(PersonNameComponents) and .unknown (:8245-8287). Both facts stand — the sample
is Apple's, and the interface is what compiles on the Mac. Prefer the interface: read the name by
switching over person.name, and treat the sample's key path as iOS-surface convenience or
sample-code drift until a matching declaration shows up.
🔴 GAP — the full input-type inventory. Apple's own code comment says "AudioSearch,
IntentPerson, and other system types may be supported as input", and the narration adds
"Check out the documentation for the full set of AudioSearch criteria." The 27.0 macOS
interface was checked on 2026-07-29 and narrows this without settling it: AudioSearch is not
present in the macOS 27.0 beta AppIntents interface at all — presumably an iOS-surface type —
so no criteria list can be read from the captures. We do not have either
list. What would resolve it: the IntentValueQuery documentation page and the AudioSearch page.
Safe default: implement values(for:) against the type Xcode's schema snippet gives you, and
switch exhaustively so a new criteria case is a compile error rather than a silent no-op.
10.6 TransientAppEntity — when a thing has no independent identity¶
Not a query protocol, but the decision that determines whether you need one at all.
✅ VERIFIED (transcript, WWDC26 344) — "A transient app entity is one that represents a temporary entity that doesn't require a unique identifier and isn't meant to be queried. That's the right fit here. In CometCal, an attendee represents a person's participation in a specific event, not the person themselves. The same person can attend multiple events, and indexing each attendance separately would create duplicative results in Spotlight. Since attendees are always accessed through the event that holds them, there's no need for an independent look up path.
TransientAppEntitymakes that explicit… no query to write, no index to maintain."
The decision rule, distilled: is this thing ever the target of a lookup, or is it only ever
reached through a parent? Only-through-a-parent means TransientAppEntity.
✅ SDK-verified shape (AppIntents-27.0-macos.swiftinterface:2688-2703): protocol
TransientAppEntity : AppEntity { init() }, macOS 13.0 / iOS 16.0 — old API, not 2026 surface —
with a default id: UUID and a synthesized default query, which is the "no query to write"
promise made literal.
// 🟡 RECONSTRUCTED — session 344 published no code block.
@AppEntity
struct AttendeeEntity: TransientAppEntity {
var person: IntentPerson
var status: AttendeeStatus
var type: AttendeeType
var isOptional: Bool
}
⚠️ This choice is not local. See §14.4: a TransientAppEntity cannot be used with any of
the three entity-annotation surfaces (notifications, Now Playing, AlarmKit). Choosing transient for
good local reasons silently forecloses system integrations for that type.
11. Shaping the response: dialog, questions, and snippets¶
Once Siri can find your entity and run your intent, the remaining question is what the user hears and sees. Session 343 is the source for all of this, and its framing is worth keeping:
✅ VERIFIED (transcript, WWDC26 343) — "Siri does the heavy lifting. It understands natural language, picks the right action, and crafts a helpful response. The App Intents framework gives you the tools to shape what Siri does, and refine how it responds."
11.1 Custom dialog — and the full: / supporting: rule¶
The baseline is to return an empty IntentResult, which "tells Siri to take care of the response
when the intent runs" — Siri writes the sentence. ✅ VERIFIED (transcript, 343).
To take it over, add ProvidesDialog and return an IntentDialog with two strings:
// ✅ VERIFIED (Apple code sample, WWDC26 343 @ 2:42)
@AppIntent(schema: .audio.addToPlaylist)
struct AddToPlaylistIntent {
func perform() async throws -> some IntentResult & ProvidesDialog {
// Adds song to playlist and responds
return .result(
dialog: IntentDialog(
full: """
Added \(song.title) to the \
\(playlist.title) mix tape.
""",
supporting: "Added"
)
)
}
}
The rule governing the split is easy to get backwards, so here it is verbatim:
✅ VERIFIED (transcript, WWDC26 343) — "Siri can show the supporting string with UI, and read the full dialog on voice-only devices like AirPods. Because of this, the full string should describe what happened on its own."
supporting:is the caption that sits next to a visual. It can be terse — "Added" — because the visual carries the rest.full:must be self-sufficient audio. Someone with AirPods in and a phone in their pocket hears only this. "Added" is a failure; "Added Aurora to the Late Nights mix tape" is not.
✅ SDK-verified (AppIntents-27.0-macos.swiftinterface:4447-4454):
IntentDialog(full:supporting:) is macOS 13.0 / iOS 16.0, with (full:systemImageName:) and
(full:supporting:systemImageName:) overloads at macOS 14.2 / iOS 17.2. The two-string split is
not new API — just newly explained.
The presenter's stated motivation for customizing at all is brand vocabulary: "I call songs tracks and playlists mix tapes." ✅ VERIFIED (transcript, 343). That is the honest reason to do this — not to be clever, but because Siri's generic phrasing will call your objects by the wrong nouns.
11.2 Asking a question mid-perform — requestValue¶
✅ VERIFIED (transcript, WWDC26 343) — "But what if you want to ask people a question while your intent is running? A well-placed clarifying question lets people finish the action they meant to take. To ask a question before your intent result, use a dialog request within your perform method."
The mechanism is the parameter projection. The @AppIntent macro exposes each parameter as
$name, and $name.requestValue(_:) is an async throws call that returns the resolved value:
// ✅ VERIFIED (Apple code sample, WWDC26 343 @ 3:42)
@AppIntent(schema: .clock.createTimer)
struct CreateTimerIntent {
// MARK: Schema Parameters
var duration: Duration
var label: String?
var isSleepTimer: Bool
func perform() async throws -> some ReturnsValue<TimerEntity> {
// Checks active timers and requests label parameter
label = try await $label.requestValue(
"""
You already have a timer running. \
What should we call this one?
"""
)
return .result(value: timerEntity)
}
}
Note that this is conditional: the question is asked only because another timer is already running. That is the pattern — inspect state, ask only when the answer is genuinely ambiguous.
Apple's own restraint advice, which belongs next to the API rather than in a design appendix:
✅ VERIFIED (transcript, WWDC26 343) — "Remember to ask clarifying questions sparingly to avoid friction."
✅ SDK-verified — the "other kinds" now have names. Session 343 says: "If you want to ask
people to choose from a list of items, or ask for a confirmation, check out the sample app
and documentation to learn about other kinds of dialog requests" — and left the symbols unnamed,
which this guide previously carried as a 🔴 GAP. The 27.0 interface declares all three on
IntentParameter, macOS 13.0 / iOS 16.0 (AppIntents-27.0-macos.swiftinterface:4367-4369):
final public func requestValue(_ dialog: IntentDialog? = nil) async throws -> Value.ValueType
final public func requestDisambiguation(among itemsToDisambiguate: [Value.ValueType], dialog: IntentDialog? = nil) async throws -> Value.ValueType
final public func requestConfirmation(for itemToConfirm: Value.ValueType, dialog: IntentDialog? = nil) async throws -> Bool
$parameter.requestDisambiguation(among:dialog:) is "choose from a list";
$parameter.requestConfirmation(for:dialog:) is the confirmation. (This closes former register
entry G15, §16.)
11.3 DisplayRepresentation — the highest-leverage thing you can customize¶
This is the item Apple puts first in its own recommended adoption order, and the reason is that it is consumed by more subsystems than anything else you write.
✅ VERIFIED (transcript, WWDC26 343) — "Entity display representation can be used in responses, like when an entity has been created or updated. They are also used when asking someone to choose between similar entities, or when answering questions about content in your app. Spotlight and Shortcuts can use them, too."
Counting the consumers named across session 343: responses, disambiguation, question-answering, Spotlight, Shortcuts — plus intent confirmations (§12.2) and the on-screen-awareness fast path (§14.3). That is seven surfaces fed by one property.
// ✅ VERIFIED (Apple code sample, WWDC26 343 @ 4:26)
// Enhanced DisplayRepresentation
@AppEntity(schema: .audio.song)
struct SongEntity {
var displayRepresentation: DisplayRepresentation {
DisplayRepresentation(
title: "\(title)",
subtitle: "\(artistName)",
image: artworkImage
)
}
}
DisplayRepresentation(title:subtitle:image:) — the title is a LocalizedStringResource-style
interpolation; subtitle and image are the enrichment. Session 344's calendar variant uses
DisplayRepresentation(title:image:) with an SF Symbol — the same initializer with subtitle:
defaulted. ✅ SDK-verified (AppIntents-27.0-macos.swiftinterface:3844-3859): the overload
set is init(title:subtitle:image:) (macOS 13 / iOS 16, subtitle and image defaulted),
init(title:subtitle:image:synonyms:) (macOS 14 / iOS 17), and a 27.0 overload taking an async
image closure. image: is a nested DisplayRepresentation.Image, built with init(named:),
init(systemName:), init(data:) or init(url:).
11.4 ⚠️ SnippetIntent is an iOS 26 feature — not new this year¶
This correction matters because SnippetIntent is routinely described in 2026 write-ups as part of
the new Siri work. It is not.
✅ VERIFIED (docs) —
/documentation/appintents/snippetintentgives availability as iOS 26.0+ / macOS 26.0+ / watchOS 26.0+ / visionOS 26.0+ / tvOS 26.0+, and the App Intents updates page lists it under June 2025.
// ✅ VERIFIED (docs) — the declaration as published
protocol SnippetIntent : AppIntent where Self.PerformResult : ShowsSnippetView
✅ SDK-verified — the interface carries the identical declaration at
@available(anyAppleOS 26.0, *) (AppIntents-27.0-macos.swiftinterface:3662-3667), plus a
static func reload() and the EmptySnippetIntent default type (:3668-3689). The 26.0 floor is
compiler-attested, not just documented.
It is prior art that the 2026 Siri work builds on, not part of this release. If you are writing
a migration plan from iOS 26, SnippetIntent is not on it — you may already have it.
Purpose, ✅ VERIFIED (docs): display "custom SwiftUI views to show people the result of their action, confirm a selection, and more." Snippets support interactive elements — buttons and toggles that trigger further app intents.
Two behaviours that bite, both ✅ VERIFIED (docs):
- The system can call a
SnippetIntentmultiple times. Yourperform()must re-fetch current state on each call so the view reflects user interaction. Do not cache. This is a real correctness trap: a snippet that caches its data will show stale state after the user taps one of its own buttons. - Snippet-only intents are non-discoverable by default. Set
isDiscoverable = trueto surface them in Shortcuts or Spotlight.
Related symbols: ShowsSnippetView (the result conformance) and EmptySnippetIntent (a
default empty implementation).
11.5 The other route: a snippet view straight from a schema intent¶
You do not need SnippetIntent to show a custom view. A schema intent can return one directly by
composing ShowsSnippetView into its result type:
// ✅ VERIFIED (Apple code sample, WWDC26 343 @ 5:05)
@AppIntent(schema: .audio.addToPlaylist)
struct AddToPlaylistIntent {
var audioEntity: AudioEntity
var playlist: PlaylistEntity
func perform() async throws -> some IntentResult & ProvidesDialog & ShowsSnippetView {
// Adds to playlist and shows dialog and snippet
let view = PlaylistSnippetView(
playlist: updatedEntity,
tracks: updated.tracks
)
return .result(dialog: dialog, view: view)
}
}
The composition is some IntentResult & ProvidesDialog & ShowsSnippetView with the matching
.result(dialog:view:) factory.
When you get one for free: session 344 notes that "By default, Siri builds the result card
from the display representation. Snippet views let me replace that with a custom SwiftUI view."
✅ VERIFIED (transcript, 344). So the ladder is: do nothing → Siri renders your
DisplayRepresentation; add ShowsSnippetView → you render.
Design advice from the same session, and it is good: "You can get really creative here… but also remember to keep it simple and lightweight." ✅ VERIFIED (transcript, 344).
11.6 Apple's own "don't" list¶
Rare enough to be worth reproducing in full, because negative guidance is scarce in this area:
✅ VERIFIED (transcript, WWDC26 343) — "When approaching customization, test your intents and decide where customization actually makes sense for your app. Make sure your responses are accurate and sound natural across all platforms, including voice-only devices like AirPods. Remember to ask clarifying questions sparingly to avoid friction. Finally, use custom visuals to bring your app's identity to Siri, keeping in mind how they'll scale across the ecosystem."
Four rules, and the second and fourth are the ones people violate: a response written for a phone screen read aloud on AirPods, and a snippet designed for an iPhone rendered on a Watch.
12. Donations, confirmations, and entity ownership¶
12.1 Interaction donations — teaching Siri what happens in your UI¶
The boundary is precise, and stating it correctly prevents both under- and over-donating:
✅ VERIFIED (transcript, WWDC26 343) — "When people interact with your app through Siri or Shortcuts, the system already knows about it. But, Apple Intelligence can't learn from actions people take through your app's UI without your help. That's where donations come in."
The mechanism:
✅ VERIFIED (transcript, WWDC26 343) — "Each donation is a hint that a person took a specific action in your app's UI. The system stores these as schema-conforming App Intents in a temporary transcript, giving Siri the context it needs to make smarter decisions."
Note "schema-conforming". Donations are expressed in the schema vocabulary — which is another way in which the schema tier is the price of admission.
Two distinct payoffs, both named:
- Preference learning and app disambiguation. "After messaging them frequently in the app, eventually, when someone says: 'Send a message to a contact from the Home Screen,' Siri might infer the right app to use for that contact."
- Live-activity awareness. "Interaction Donations also keep Siri aware of ongoing
activities in your app." The example: a
NavigationSessionstarted in the app's UI is donated; the user then asks Siri to add a stop, and "Thanks to the Interaction Donation, Siri can know what NavigationSession is active in the app."
✅ VERIFIED (transcript, 343) for both.
⚠️ Payoff 2 is explicitly scoped — it is a hard limit, not a general capability:
✅ VERIFIED (transcript, WWDC26 343) — "This pattern applies to intents that start or stop NavigationSessions in the Maps domain, and stop, start, pause, or lap stopwatches in the Clock domain."
Two domains. Navigation sessions and stopwatches. If you were hoping to donate "a workout is in progress" or "a document is open" and have Siri reason about it, that is not what this does.
// ✅ VERIFIED (Apple code sample, WWDC26 343 @ 7:44)
@ModelActor
actor ModelManager {
func sendMessage(_ /* ... */, donateIntent: Bool = false) async throws -> [Message.ID] {
// Donate intent with parameters and result so Siri can learn user preferences
if donateIntent {
let intent = SendMessageIntent()
intent.destination = .recipients(conversation.recipients.map(\.entity))
let result = messages.map(\.entity)
Task {
try await IntentDonationManager.shared.donate(
intent: intent,
result: .result(value: result)
)
}
}
}
}
API: IntentDonationManager.shared.donate(intent:result:), async throws. The result is passed
as .result(value:) — you donate the outcome, not just the invocation.
The pattern worth copying is the donateIntent: Bool = false flag. One shared helper serves
both the UI path and the intent path, and only the UI call site passes true. The rationale,
verbatim: "Apple Intelligence already learns from Siri interactions, so I only need to donate
UI interactions." ✅ VERIFIED (transcript, 343).
Getting this wrong double-counts: donate from inside perform() and every Siri-originated action
is recorded twice.
12.2 Confirmations and OwnershipProvidingEntity¶
The security framing is unusually direct about why this exists:
✅ VERIFIED (transcript, WWDC26 343) — "Asking people to confirm that the action looks right keeps them informed and protects them from unintended side effects, which are a known risk with Large Language Models."
And the default policy, which is the part that should make you sit up:
✅ VERIFIED (transcript, WWDC26 343) — "By default, Siri assumes your entities are private to the person, and may skip confirmations for them."
The absence of the protocol is a decision, not a neutral default. If your entities can be shared or made public and you do not say so, Siri may act on them without asking.
// ✅ VERIFIED (Apple code sample, WWDC26 343 @ 10:03)
// Informs system if entity is public or shared with others
@AppEntity(schema: .calendar.event)
struct EventEntity: OwnershipProvidingEntity {
var ownership: EntityOwnership {
// isShared used to compute ownership state: .shared, .public, or .unknown
attendees.isEmpty ? .unknown : .shared
}
}
Confirmed API:
- protocol OwnershipProvidingEntity
- requirement var ownership: EntityOwnership
- EntityOwnership members named in Apple's own code comment: .shared, .public,
.unknown
✅ SDK-verified (AppIntents-27.0-macos.swiftinterface:10651-10668, anyAppleOS 27.0):
protocol OwnershipProvidingEntity : AppEntity with exactly that requirement. One shape
correction to the sample's comment: EntityOwnership is an OptionSet, not an enum — static
members .unknown, .shared and .public — and no .private member exists in the 27.0
interface. "Private" is the implicit default when you do not adopt the protocol, exactly as
the research pass recorded; .unknown is the spelling for "I cannot say". (This closes former
register entry G17, §16.)
Two scoping rules, both explicit:
✅ VERIFIED (transcript, WWDC26 343) — "Only add the protocol to entities that people can share or make public in your app. Then, provide the ownership state. Keep the ownership state up to date whenever the system requests an entity from your app. This ensures Siri has the necessary information when deciding to confirm."
The worked example of the resulting policy: "Siri may not confirm when I update a personal
event, but it may confirm when I ask it to update Crew Lunch since I'm updating an event with
attendees." ✅ VERIFIED (transcript, 343). And confirmations reuse your
DisplayRepresentation as their visual — the seventh consumer from §11.3.
Note the second rule carefully: ownership is computed every time the system requests the entity,
not once at creation. In the sample it is a computed property over attendees, which is exactly
right. A stored let ownership set at init would go stale the moment someone shares the event.
12.3 Where confirmations come for free¶
Not everything needs OwnershipProvidingEntity. Session 344 notes that for a delete intent —
parameters being just the event and an optional span for recurring events — "Siri
automatically handles the confirmation dialog before anything is removed." ✅ VERIFIED
(transcript, 344). The same demo shows Siri "makes sure to disambiguate when more than one event
matches."
So destructive schema verbs get confirmation behaviour from the schema itself. What
OwnershipProvidingEntity adds is the sharing dimension that the schema cannot infer.
13. The new execution model¶
Everything in this section is new in the 27 releases and comes from WWDC26 session 345, which refers to them as "our 2027 releases" — see the version-floor box at the top of this guide before reading anything into that phrasing.
Session 345 is the one that is not Siri-specific. Sessions 240, 343 and 344 are about making Siri understand your app; 345 is about the App Intents framework as an execution substrate — process selection, background execution, parameter resolution cost, cross-app value transfer. Much of it applies whether or not you ever adopt a schema.
⚠️ Session 345 explicitly does not cover UndoableIntent, IntentModes or SnippetIntent,
despite the first two appearing on the App Intents updates page. The SDK pass now supplies the
first two's shapes, which is enough to plan against but no substitute for usage guidance —
✅ SDK-verified: UndoableIntent : SystemIntent with a @MainActor var undoManager:
UndoManager?, anyAppleOS 26.0 (AppIntents-27.0-macos.swiftinterface:3707-3716); and
IntentModes, an OptionSet — .background, .foreground, .foreground(.immediate) /
(.deferred) / (.dynamic) — consumed by static var supportedModes on AppIntent,
anyAppleOS 26.0 (:3106-3107, :3250-3277). 🔴 GAP: neither has session coverage or a
usage example in our corpus, and SnippetIntent aside (§11.4), the semantics remain
undocumented here. Safe default: the shapes above are compiler-truth; do not design around
them from the shape alone.
13.1 ValueRepresentation — sharing structured types across apps¶
The problem is stated precisely, and it is a real hole in Transferable:
✅ VERIFIED (transcript, WWDC26 345) — "Maps needs some structured information — a coordinate, an address, or something it can navigate to. But that kind of data doesn't have an associated data format that can be put in a file or data. The existing file and data representations work great for known formats like PDFs or images — but not for structured types that don't have any."
✅ VERIFIED (transcript, WWDC26 345) — "This is where
ValueRepresentationcomes in. It's a new representation type that lets you share structured types that the system already understands."
// ✅ VERIFIED (Apple code sample, WWDC26 345 @ 0:01)
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
)
}
)
}
}
// If the entity already has a PlaceDescriptor property, use a key-path — much less code:
struct LandmarkEntity: AppEntity, Transferable {
var id: Int
@Property var placeDescriptor: PlaceDescriptor
static var transferRepresentation: some TransferRepresentation {
ValueRepresentation(exporting: \.placeDescriptor)
}
}
Confirmed API:
- ValueRepresentation(exporting:) writable two ways — a closure form and a key-path form
(per the SDK, one closure-form initialiser that key-path literals convert into; see the
resolution box below).
- It composes: "I just need to add a ValueRepresentation alongside any existing
representations." ✅ VERIFIED (transcript, 345). Your FileRepresentation and
DataRepresentation stay.
- PlaceDescriptor(representations:commonName:) comes from GeoToolbox, with
.coordinate(CLLocationCoordinate2D) as a representation case.
Apple's own recommendation: "If my entity already has a PlaceDescriptor @Property, I can skip
the closure entirely and use a key-path. Same result, much less code." ✅ VERIFIED (transcript,
345).
✅ ValueRepresentation versus IntentValueRepresentation — resolved: they are one type¶
This was this guide's flagged naming hazard, and the SDK pass settles it.
Session 240 uses IntentValueRepresentation for what looks like the same job — exporting a
ContactEntity as an IntentPerson:
// ✅ VERIFIED (Apple code sample, WWDC26 240 @ 18:18)
// Working across apps - Exporting content to another app
extension ContactEntity: Transferable {
static var transferRepresentation: some TransferRepresentation {
IntentValueRepresentation(
exporting: \.person
)
}
}
// ✅ VERIFIED (Apple code sample, WWDC26 240 @ 20:00)
// Working across apps - IntentValueRepresentation
extension ContactEntity: Transferable {
static var transferRepresentation: some TransferRepresentation {
IntentValueRepresentation(exporting: \.person, importing: { intentPerson in
let contact = Contact(importing: intentPerson)
ContactManager.shared.contacts.append(contact)
return contact.entity
})
}
}
Session 345 uses ValueRepresentation(exporting:). Both appear in Apple's own published code
samples, so neither is a transcription artifact.
✅ SDK-verified — same type. The SDK interface dump this guide asked for now exists, and the answer is (b), an alias:
// ✅ SDK-verified (AppIntents-27.0-macos.swiftinterface:2629-2650)
extension AppEntity {
public typealias ValueRepresentation = IntentValueRepresentation
}
public struct IntentValueRepresentation<Item, IntentValue> : TransferRepresentation
where Item : Transferable, IntentValue : _IntentValue, IntentValue : Sendable {
// init(exporting:) and init(exporting:importing:), constrained to system intent
// values — with a dedicated extension for IntentValue == IntentPerson
}
That also explains each session's spelling: session 345 writes ValueRepresentation inside an
entity declaration, where the AppEntity-scoped typealias resolves; session 240 writes the
underlying name, which works anywhere. They cannot diverge — port code freely between the two
spellings, remembering only that the short one resolves inside AppEntity-conforming scope. The
key-path call sites (exporting: \.person) compile against the closure-form initialisers via
Swift's key-path-as-function conversion; there is no separate key-path overload in the interface.
⚠️ One availability surprise: the interface annotates the whole cluster
@available(anyAppleOS 26.4, *) — not 27.0 — yet none of it appears in this repo's 26.5
interface capture. Treat the 27.0 SDK's annotation as the deployment floor Xcode 27 will enforce,
and treat "new in the 27 releases" as true of the SDK that declares it rather than of the
availability number it carries. (This closes former register entry G4, §16.)
The import decision, which is genuinely crisp¶
Independent of the naming question, session 240 gives a clean rule for the receiving side:
✅ VERIFIED (transcript, WWDC26 240) — "When content comes into your app, there are usually two possibilities. Either that content refers to something that already exists, or it represents something entirely new. You get to decide which path your app takes. If you're matching existing content, use
IntentValueQuery. If you're creating something new, useimportingon thetransferRepresentation."
| Incoming content | Mechanism |
|---|---|
| refers to something you already have | IntentValueQuery (§10.5) |
| represents something new | importing: on the transfer representation |
And: "Many apps use both, depending on the intent and workflow." ✅ VERIFIED (transcript,
240). exporting: alone means resolve-or-nothing; adding importing: means the receiving app
creates something.
The framing that makes the whole design click:
✅ VERIFIED (transcript, WWDC26 240) — "Your app doesn't need to know what happens next. It just needs to describe its content accurately."
13.2 The three discovery mechanisms — and where RelevantEntities fits¶
Session 345 argues for a third mechanism by showing what the first two cannot do:
✅ VERIFIED (transcript, WWDC26 345) — "But what about that new playlist? Nobody's searched for it in Spotlight since they don't know it exists. And since nobody's played it, there's no interaction to donate either. You need a way to tell the system this playlist is relevant so it can surface it at the right moment."
That is the cold-start problem, stated exactly. Spotlight indexing pays off when a user goes looking. Donation pays off after a user has behaved. Neither works for content that is new.
// ✅ VERIFIED (Apple code sample, WWDC26 345 @ 5:18)
// Suggest playlists for the workout session
let playlistEntities = [dailyRun, runningMix]
let workoutContext = AppEntityContext.audio(.workout(activityType: .running))
try await RelevantEntities.shared.updateEntities(
playlistEntities, for: workoutContext
)
// Clear all entities for a context
try await RelevantEntities.shared.removeAllEntities(for: workoutContext)
// Remove specific entities from a context
try await RelevantEntities.shared.removeEntities(playlistEntities, from: workoutContext)
// Or remove all entities across all contexts
try await RelevantEntities.shared.removeAllEntities()
Confirmed API:
- RelevantEntities.shared — a singleton.
- updateEntities(_:for:), removeAllEntities(for:), removeEntities(_:from:),
removeAllEntities() — all async throws. ✅ SDK-verified
(AppIntents-27.0-macos.swiftinterface:4837-4846, anyAppleOS 27.0), plus a fifth method the
session did not show: removeEntities(_:) async throws, no context parameter.
- AppEntityContext — an opaque Hashable struct with domain-scoped factory methods. One
concrete path verified from the session: AppEntityContext.audio(.workout(activityType:
.running)) — contexts are domain-scoped (.audio) with a situation (.workout)
carrying parameters (activityType:). ⚠️ But see the gap box below: on the macOS surface
the interface spells only .audio(.nowPlaying).
⚠️ Lifecycle rule with no safety net: "Entities stay registered until you remove them." ✅ VERIFIED (transcript, 345). There is no TTL. Registration is a memory-management obligation on your app: register when a thing becomes situationally relevant, remove when it stops. A bug here does not throw; it just leaves stale suggestions in other apps' UI indefinitely.
🔴 GAP — the full AppEntityContext inventory — narrowed but not closed. The 27.0 macOS
interface was checked on 2026-07-29: AppEntityContext's only factory on that surface is
static func audio(_: AudioContext), and AudioContext's only member is .nowPlaying
(AppIntents-27.0-macos.swiftinterface:4814-4837). The session's .workout(activityType:)
situation is not present in the macOS 27.0 beta interface — presumably an iOS-surface case
this repo has not captured. What would resolve it: the iOS interface, or the AppEntityContext
documentation page. Safe default: rely on Xcode completion at the call site; do not invent a
context path — on the Mac, .audio(.nowPlaying) is the only spellable context today.
The three-way decision rule — the cleanest taxonomy in the whole session set:
✅ VERIFIED (transcript, WWDC26 345) — "Use Spotlight when you want your content to be searchable and retrievable by Siri. Use interaction donation to teach Siri and the system how people use your app — so it can identify patterns and suggest actions people may want to repeat. And use
RelevantEntitiesto hint to the system which content is relevant in specific situations — so the system can suggest it at the right moment."
| Mechanism | Job | Trigger |
|---|---|---|
Spotlight / IndexedEntity |
findability | user searches |
| Interaction donation | behavioural learning | user has done it before |
RelevantEntities |
situational suggestion | a situation arises |
One under-appreciated detail: the consumer surface demonstrated was the Fitness app's suggested
playlists when setting up a running workout. ✅ VERIFIED (transcript, 345). So RelevantEntities
feeds other apps' suggestion UI, not only Siri. That is a genuinely different distribution
channel, and it is available to apps in uncovered categories (§6) — registering relevance does not
require a schema.
13.3 ⚠️ EntityCollection — the parameter-resolution performance cliff¶
This section documents a cost that is otherwise completely invisible.
✅ VERIFIED (transcript, WWDC26 345) — "Before an intent runs, the system resolves every entity. That means calling the entity query to populate all of its properties, so the intent has everything it may need. For most intents, that's exactly what you want. But in my case, this meant resolving hundreds or thousands of photo entities, even though my code only needs the entity ID to update my data model."
Read that again if you have an intent taking [SomeEntity]. Every element is fully resolved
through your query before perform() is entered. If your query hits a database, that is N round
trips you never wrote and cannot see in your own code.
✅ VERIFIED (transcript, WWDC26 345) — "
EntityCollectionfixes this. It's a new type that stores an array of entity identifiers, instead of the fully resolved entities. When you useEntityCollectionas your parameter type, the system passes just the identifiers to the intent's perform method, without resolving the full entities."
// ✅ VERIFIED (Apple code sample, WWDC26 345 @ 7:15)
struct TagPhotosIntent: AppIntent {
static let title: LocalizedStringResource = "Tag Travel Photos"
@Parameter var photos: EntityCollection<PhotoEntity> // was: [PhotoEntity]
@Parameter var tag: String
func perform() async throws -> some IntentResult {
modelData.tagPhotos(ids: photos.identifiers, tag: tag) // was: tagPhotos(photos, tag: tag)
return .result()
}
}
Confirmed API: EntityCollection<E>, generic over the entity type, with an .identifiers
property. It is a drop-in replacement for [E] as a @Parameter type.
✅ SDK-verified (AppIntents-27.0-macos.swiftinterface:10284-10328, anyAppleOS 27.0):
struct EntityCollection<Entity: AppEntity> with var identifiers: [Entity.ID],
init(identifiers: [Entity.ID] = []), init(entities: [Entity]), count / isEmpty,
append / remove / contains for both identifiers and entities, and a Collection conformance
over Entity.ID — plus an escape hatch the session did not mention: func resolvedEntities()
async throws -> [Entity], for the moment inside perform() when you discover you need the full
entities after all.
Adoption criterion, distilled: use EntityCollection whenever perform only needs IDs.
Measured claim — read the attribution carefully. Apple's stated result: "I built a Shortcut to
find and tag 1000 photos. First, with a regular array of photo entities. Then with
EntityCollection, which was almost instant." ✅ VERIFIED (transcript, 345). Apple-published,
qualitative only — no absolute numbers, no hardware, no OS build given in the session. "Almost
instant" is the ceiling of what can honestly be claimed; do not repeat it as a multiplier.
13.4 @UnionValue — one parameter, several entity types¶
✅ VERIFIED (transcript, WWDC26 345) — "A union value is a Swift enum where each case wraps a different type, letting a single parameter represent one of several options."
✅ VERIFIED (transcript, WWDC26 345) — "With
@UnionValuesupporting input parameters, I can use one widget for both."
// ✅ VERIFIED (Apple code sample, WWDC26 345 @ 11:58)
@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"
]
}
Confirmed API:
- macro @UnionValue on an enum whose cases each wrap exactly one type.
- What it generates, verbatim: "The macro generates everything the system needs — type
information, case metadata, and picker support."
- static let typeDisplayRepresentation: TypeDisplayRepresentation — "the label for the
overall type."
- static let caseDisplayRepresentations: [Cases: DisplayRepresentation] — "maps each case
to the name shown in the picker."
The non-obvious detail: the dictionary key type is a macro-synthesized Cases type,
referenced as .landmarkCollection / .photoAlbum — not the enum itself. The enum has payloads
so it cannot be trivially Hashable; hence a separate caseless mirror. If you write
[TravelGalleryContent: DisplayRepresentation] it will not compile, and the reason will not be
obvious.
✅ SDK-verified (AppIntents-27.0-macos.swiftinterface:5159, :3716-3736): the macro attaches
conformance to AppUnionValue (anyAppleOS 27.0), whose requirements are exactly the two statics
above — and Cases is a real associated type, constrained to AppUnionValueCasesProviding :
AppEnum, which confirms the caseless-mirror reading: the dictionary key type is Self.Cases, and
it is itself an AppEnum.
What is actually new here. Session 343 uses AudioEntity as "a UnionValue type that includes
both songs and playlists" as an IntentValueQuery return element type. Session 345 frames
input parameters as the new capability. Cross-referencing the two: @UnionValue enums work
both as query return types (which predates this release) and as @Parameter input types (new).
That split is now SDK-attested rather than inferred: the 26.5 interface already carries the
@UnionValue macro, conforming only to _IntentValueRepresentable
(AppIntents-26.5-macos.swiftinterface:10344); the 27.0 interface adds the AppUnionValue
conformance that brings Cases, display representations and picker support. The macro is old; the
input-parameter machinery is 27.0.
Scope: "this isn't limited to Widgets — @UnionValue parameters work everywhere your intent
does, including the Shortcuts app." ✅ VERIFIED (transcript, 345).
Where you have already met unions in this guide: .audio.addToPlaylist takes an AudioEntity
union (§5.1), and .calendar.event has two union-typed properties — location as
PlaceDescriptor | String and alarms as Duration | Date (§5.1). Apple's own schemas use
unions, so you will consume them before you author one.
13.5 SyncableEntity — cross-device Siri conversations¶
✅ VERIFIED (transcript, WWDC26 345) — "With our 2027 releases, Siri can continue conversations across devices — and your entities can be part of those conversations."
The failure mode this addresses:
✅ VERIFIED (transcript, WWDC26 345) — "If I ask Siri on my iPhone to add a photo to an album, then switch to my other device and ask Siri to tag that photo — Siri might not be able to find that photo. … Your entity's ID might be generated locally on each device. Local IDs work great on the device they were created on. But each device generates its own local IDs. So the same entity can end up with a different ID on each device."
// ✅ VERIFIED (Apple code sample, WWDC26 345 @ 10:14)
// If your ID is already stable across devices (server UUID, CloudKit record ID):
struct PhotoEntity: AppEntity, SyncableEntity {
var id: Int // Already stable across devices — that's it
}
// If you use local IDs, pair a local and a stable ID:
struct PhotoEntity: AppEntity, SyncableEntity {
var id: SyncableEntityIdentifier<String, String>
init(localID: String, stableID: String) {
self.id = SyncableEntityIdentifier(local: localID, stable: stableID)
}
}
Confirmed API:
- protocol SyncableEntity — "it declares to the system that your entity's ID is stable
and can be used across devices."
- SyncableEntityIdentifier<Local, Stable> — two generic parameters, init(local:stable:).
- Division of labour, verbatim: "On-device, your code uses the local ID. And across devices, the
system uses the stable one."
✅ SDK-verified (AppIntents-27.0-macos.swiftinterface:2651-2687, anyAppleOS 27.0):
protocol SyncableEntity : AppEntity is an empty marker — it declares, literally — and
struct SyncableEntityIdentifier<LocalID, StableID> (the SDK's generic parameter names) requires
both to be EntityIdentifierConvertible & Sendable, stores local / stable as optionals, and
adds a convenience init(id:) when LocalID == StableID.
Named sources of stable IDs: "That could come from your server, or from CloudKit record IDs." Named source of unstable IDs: "local identifiers, like CoreData row IDs." ✅ VERIFIED (transcript, 345).
Read the first word of the protocol description again: it declares. SyncableEntity is not a
synchronization mechanism. It does not make your IDs stable. It tells the system they already are.
See §14.5 for why that is a silent failure waiting to happen.
13.6 Native @Parameter types¶
✅ VERIFIED (transcript, WWDC26 345) — "When you declare a
@Parameter, the system gives you a native picker, Siri understanding, and localization for free. We're extending that same support to more native types. We're adding native support forDuration, so no more building custom time pickers. AndPersonNameComponentsfor structured name input instead of a plain string. And more."
Two types named explicitly: Duration and PersonNameComponents.
✅ SDK-verified — with an availability surprise. The interface conforms both to the
intent-value machinery at @available(anyAppleOS 26.0, *), not 27.0: Duration : _IntentValue,
DisplayRepresentable (AppIntents-27.0-macos.swiftinterface:5074-5078) and PersonNameComponents
: _IntentValue, DisplayRepresentable (:5090-5094). Session 345 presents them as part of this
year's extension; the SDK dates the conformances to 26.0. And one member of "and more" is
findable: Calendar.RecurrenceRule : IntentValueConvertible at anyAppleOS 27.0 (:5071-5073)
— the Foundation type §5.1's .calendar.event uses.
🔴 GAP: the rest of "and more" was not enumerated — the 27.0 interface was checked on
2026-07-29 and a complete inventory of native @Parameter types was not extracted from it. What
would resolve it: the App Intents updates page, the @Parameter documentation, or a systematic
sweep of the interface's _IntentValue conformances. Safe default: try the type; if it does
not get a picker, fall back to a supported representation.
Corroboration for Duration from two other sessions, which is a good sign the claim is real:
.clock.createTimer declares var duration: Duration (session 343, §5.1), and CometCal's event
alarm union includes Duration (session 344, §5.1).
The payoff restated: "Each one gets a native picker and works everywhere your intent does — Siri, Shortcuts and Widgets." ✅ VERIFIED (transcript, 345).
13.7 LongRunningIntent — past the 30-second wall¶
The limit, stated as a hard number:
✅ VERIFIED (transcript, WWDC26 345) — "When your intent runs — from Siri, Shortcuts, or any system surface — it only has 30 seconds to finish. That works for most everyday actions. But not every intent is that quick."
That is the clearest statement of the App Intents execution ceiling in our corpus, and it is worth
knowing even if you never adopt LongRunningIntent: 30 seconds, from any surface.
✅ VERIFIED (transcript, WWDC26 345) — "
LongRunningIntentfixes this. It lets your intent run beyond the 30-second limit — and manages the background task lifecycle of your app. And as your intent runs, progress updates appear automatically as a Live Activity."
// ✅ VERIFIED (Apple code sample, WWDC26 345 @ 13:41)
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)")
}
}
Confirmed API:
- protocol LongRunningIntent — ✅ SDK-verified: protocol LongRunningIntent :
ProgressReportingIntent, anyAppleOS 27.0 (AppIntents-27.0-macos.swiftinterface:3601-3604).
It refines ProgressReportingIntent — macOS 14 / iOS 17, itself refining AppIntent and
supplying the implicit progress: Foundation.Progress member (:3625-3634) — so the
narration's "because it builds on ProgressReportingIntent" is literal, and this guide's
earlier 🟡 "appears to refine AppIntent" is superseded.
- protocol CancellableIntent with an onCancel handler, supplied here as the trailing
closure of performBackgroundTask. ✅ SDK-verified at anyAppleOS 26.4 (:3369-3376),
along with a standalone withIntentCancellationHandler(operation:onCancel:isolation:).
- performBackgroundTask { … } onCancel: { reason in … } — async throws, generic over the
body's return type. ✅ SDK-verified (:3605-3611): performBackgroundTask<T>(options:
LongRunningTaskOptions = [], operation:) async throws -> T, with the onCancel: overload gated
where Self : CancellableIntent — which is why the sample conforms to both protocols.
- progress — an implicit member with totalUnitCount / completedUnitCount as Int64,
available "because it builds on ProgressReportingIntent." ✅ SDK-verified: it is
Foundation.Progress, from the ProgressReportingIntent extension (:1889-1894).
⚠️ Progress reporting is not optional and not decorative:
✅ VERIFIED (transcript, WWDC26 345) — "
LongRunningIntentrequires the intent to report progress, so the system knows it's still working and hasn't stalled."
It is the liveness signal. An intent that adopts LongRunningIntent and never touches
progress should be assumed to get killed. See §14.6.
Cancellation:
✅ VERIFIED (transcript, WWDC26 345) — "
CancellableIntentlets your intent clean up gracefully when cancelled — whether the person tapped cancel, the system timed out or needed to reclaim resources. … the handler gives me the reason, and I can use it to cleanup partial uploads or cancel in-flight requests."
Three causes named: user-initiated, system timeout, resource reclamation. ✅ SDK-verified —
the type this guide could not previously place in a signature is IntentCancellationReason: a
Sendable, Equatable struct at anyAppleOS 26.4, and it is exactly what the onCancel:
handler receives (AppIntents-27.0-macos.swiftinterface:3377-3389, :3610). Its visible members
are .timeout and .userCancelled — two, not three: the interface has no distinct
member for resource reclamation, which presumably surfaces as .timeout or a non-public value.
(This closes former register entry G9's naming half; the reclamation mapping stays open.)
UI consequence worth designing for: "there's a stop button right on the Live Activity, so the person can cancel it at any time." ✅ VERIFIED (transcript, 345). Your cancellation path is user-reachable by default, so it will be exercised.
Background GPU — the capability that matters for this series:
✅ VERIFIED (transcript, WWDC26 345) — "
LongRunningIntentalso supports background GPU access on supported devices — for tasks like photo processing or on-device inference. Just make sure to add GPU access to your app's entitlement."
This is a supported path to running on-device inference from a background App Intent — relevant
to anyone wiring Foundation Models, Core AI or MLX behind a Shortcut. The SDK now supplies the
request side: ✅ SDK-verified, GPU access is asked for as an option flag —
performBackgroundTask(options: [.requiresGPU]) { … }, via LongRunningTaskOptions
(AppIntents-27.0-macos.swiftinterface:3612-3624). Two gates remain underspecified: "supported
devices" (unnamed) and the GPU-access entitlement (🔴 GAP — the name is not given in the
session and appears nowhere in the 27.0 interface, checked 2026-07-29; the forum corpus records
continued-processing.gpu as an existing background-GPU entitlement, from a developer post rather
than from Apple, so treat that as a lead, not the answer).
13.8 ExecutionTargets — choosing the process¶
The setup is a real architectural problem, not a toy:
✅ VERIFIED (transcript, WWDC26 345) — "When your intents, entities, and queries live in a shared package like this — linked by your app and extensions — the system has to decide which process runs each intent when a request comes in. It picks a target based on heuristics like if the app is already running, it prefers the app. and if not, it launches the extension. But sometimes that's not the right choice."
The concrete failure: "My widget shares the data model with the app — but having two processes write to the same data store can cause conflicts. So I gave the widget read-only access and the main app handles all the writes." ✅ VERIFIED (transcript, 345).
// ✅ VERIFIED (Apple code sample, WWDC26 345 @ 16:54)
// 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 }
}
// Display-only — runs in the widget extension
struct GetLandmarkStatusIntent: AppIntent {
static var allowedExecutionTargets: ExecutionTargets { .widgetKitExtension }
}
// Works in either — lets the system choose
struct TagPhotosIntent: AppIntent {
static var allowedExecutionTargets: ExecutionTargets { [.main, .appIntentsExtension] }
}
Confirmed API — with the real type name, from the SDK:
- static var allowedExecutionTargets: ExecutionTargets — ✅ SDK-verified, declared on
AppIntent and on EntityQuery (your queries can be pinned too), both anyAppleOS 27.0
(AppIntents-27.0-macos.swiftinterface:3111-3112, :4205-4206).
- ExecutionTargets is a genuine OptionSet — ✅ SDK-verified — and its real name is
IntentExecutionTargets (:3570-3597); ExecutionTargets is a typealias for it scoped
inside AppIntent (:3120-3122), which is why the sample's spelling compiles inside an intent
and why diagnostics will say IntentExecutionTargets.
- Members confirmed: .main, .appIntentsExtension, .widgetKitExtension — plus a
.default the session did not mention (:3574-3585).
✅ VERIFIED (transcript, WWDC26 345) — "With
ExecutionTargets, you override the system's heuristics and control exactly which process handles your intent."
The decision rule that falls out: pin .main for anything that writes; allow the extension
for anything that is standalone or read-only; leave it unset when you genuinely do not care.
The default heuristic — prefer the app if it is running, else launch the extension — is fine until
two processes touch the same store, at which point it is a data-corruption source that reproduces
only when the app happens to be running.
13.9 Apple's own recommended adoption orders¶
Two sessions close with an ordered list. They are worth following because they are ordered by leverage, not by API novelty.
Session 343 — for Siri integration (✅ VERIFIED, transcript, verbatim in order):
- "a great place to start is by customizing your entity display representations. They are used to display your entities across the system."
- "From there, add your entities to the semantic index, and keep the index up to date, so Siri can always find your freshest content."
- "You might also consider making your entities accessible through Siri with an
IntentValueQueryand in-app search." - "annotating your views, activities, and your existing system integrations with entities."
- "When you're ready, look into donating UI interactions."
Donations are last. DisplayRepresentation is first — consistent with it feeding seven
subsystems (§11.3).
Session 345 — for framework capabilities (✅ VERIFIED, transcript, verbatim in order):
- "add
ValueRepresentationto your entities so they can carry structured data across apps." - "Register relevant content with the system — so it gets surfaced at the right moment."
- "Adopt
EntityCollectionto make your intents faster when working with large numbers of entities." - "add
LongRunningIntentto any intent that needs more than 30 seconds to finish."
Session 240 — for the overall integration (✅ VERIFIED, transcript, verbatim in order):
- "Model and index your entities to Spotlight so Siri can find your content."
- "Adopt app schema domains that match your app's core experiences."
- "Adopt
Transferableto enable content import and export." - "Test early and often using AppIntentsTesting, then Shortcuts, Spotlight, and Siri."
14. Silent failures¶
The defining property of this stack is that most defects do not throw. This section collects the ones we can evidence. The first is the worst.
14.1 ⚠️ SILENT FAILURE — IntentParameter.valueState: "clear it" and "don't touch it" are not the same thing¶
This is the most consequential API detail in the entire App Intents session set, and every update-style intent written the obvious way gets it wrong.
Consider an update intent with an optional parameter — a due date, a recurrence rule, a location, a label. The user says one of two things:
- "Remove the due date from that reminder."
- "Change the title of that reminder."
In both cases your dueDate parameter arrives as nil. In the first case, nil means the user
explicitly asked you to clear it. In the second, nil means the user never mentioned it and
you must leave it alone.
A nil check cannot distinguish these. Session 344 states the problem in exactly those terms:
✅ VERIFIED (transcript, WWDC26 344) — "there's one important subtlety with optional parameters in update intents worth calling out. For example, when
recurrenceis nil, does that mean "don't change it" or "remove it"? A simple nil check doesn't tell me which case I'm dealing with. … the@AppIntentmacro wraps each property in anIntentParameterwhich exposes avalueState. This is how I tell the difference..setwith an actual value means a new value is provided..setwith a nil value means it's explicitly cleared..unsetmeans the parameter isn't part of the request."
Confirmed API: $parameter.valueState, an enum with .set(T?) and .unset.
✅ SDK-verified (AppIntents-27.0-macos.swiftinterface:4351-4361):
IntentParameter.ValueState is case unset / case set(Value) — for an optional parameter
Value is the optional, which is what makes .set(nil) expressible — and the extension
declaring it is @available(macOS 15.2, iOS 18.2, watchOS 11.2, tvOS 18.2, visionOS 2.2, *). The
mechanism predates this release cycle; the sessions did not invent it, they finally explained it.
valueState |
The user meant | Correct action |
|---|---|---|
.set(value) |
"make it this" | assign it |
.set(nil) |
"clear it" | delete / clear the field |
.unset |
"I didn't mention it" | leave untouched |
The wrong code — and it is the code everyone writes:
// ❌ WRONG. Compiles. Runs. Never errors. Silently ignores "remove the due date".
func perform() async throws -> some IntentResult {
var updates = ReminderUpdates()
if let title { updates.title = title }
if let dueDate { updates.dueDate = dueDate } // <- the bug
if let recurrence { updates.recurrence = convert(recurrence) }
try await store.apply(updates, to: reminder)
return .result()
}
When the user says "remove the due date", dueDate is nil, the if let fails, updates.dueDate
is never touched, the store is updated with no change to the due date, and the intent returns
success. Siri confirms the action. The due date is still there. Nothing anywhere reports a
problem.
The right code:
// 🟡 RECONSTRUCTED shape (session 344 published no code block); the three-case
// semantics are ✅ VERIFIED from narration, quoted above.
func perform() async throws -> some IntentResult {
var updates = ReminderUpdates()
switch $title.valueState {
case .set(let newTitle?): updates.title = .set(newTitle) // change it
case .set(nil): updates.title = .clear // clear it
case .unset: break // don't touch it
}
switch $dueDate.valueState {
case .set(let date?): updates.dueDate = .set(date)
case .set(nil): updates.dueDate = .clear
case .unset: break
}
switch $recurrence.valueState {
case .set(let rule?): updates.recurrence = .set(convert(rule))
case .set(nil): updates.recurrence = .clear
case .unset: break
}
try await store.apply(updates, to: reminder)
return .result()
}
Note that the fix does not stop at the intent. Your data layer needs a three-state update type
too. If ReminderUpdates.dueDate is a plain Date?, you have merely moved the ambiguity one
layer down. That is why the listing above uses a .set / .clear / absent representation on the
update struct — some form of that is required, whether you spell it as an enum, an
Optional<Optional<Date>>, or a separate clearedFields: Set<Field>.
Apple's generalization:
✅ VERIFIED (transcript, WWDC26 344) — "This pattern applies to any optional parameter where clearing the value is a meaningful action."
Blast radius. Count the update* and delete* verbs in §5.4: .calendar.updateEvent,
.clock.updateAlarm, .clock.updateTimer, .mail.updateDraft, .mail.updateMail,
.messages.editSentMessage, .notes.updateNote, .photos.updateAlbum, .photos.updateAsset,
.photos.updateRecognizedPerson, .reminders.updateGroup, .reminders.updateList,
.reminders.updateReminder, .reminders.updateSection, .books.updateSettings and its four
spacing siblings, .browser bookmark edits, .journal.updateEntry, .presentation.update,
.spreadsheet.update/.updateSheet, .whiteboard.updateBoard/.updateItem. Every one of them
is exposed. If you adopt an update schema, audit it for this before you ship.
Why it is a silent failure and not merely a bug: there is no diagnostic. It compiles cleanly,
there is no runtime warning, perform() returns success, Siri says "OK", and the user believes the
change happened. The only way to find it is to specifically test the phrase "remove the X" and then
go and look at the data.
14.2 ⚠️ Excessive interaction donations are silently ignored¶
✅ VERIFIED (transcript, WWDC26 343) — "Your interaction donations should accurately represent real user behavior in your app. If your app donates excessively, the system may ignore those donations."
No threshold is published. No error is surfaced. No API tells you your donations were dropped. An app that donates on every scroll, every selection, every view appearance can end up with worse Siri behaviour than one that donates nothing, and there is no signal to diagnose it from.
🔴 GAP: the threshold, the window and whether it is per-app or per-intent are all unknown.
Safe default: donate only completed, user-initiated, meaningful actions from the UI, never from
inside perform(), and treat donation as a rare event rather than telemetry.
14.3 ⚠️ Per-row .appEntityIdentifier loses selected and scrolled-off entities¶
Session 240 shows the per-row form inside a ForEach:
// ✅ VERIFIED (Apple code sample, WWDC26 240 @ 17:19) — the SIMPLE form
List {
ForEach(messages) { message in
MessageRow(message: message)
.appEntityIdentifier(
EntityIdentifier(
for: MessageEntity.self,
identifier: message.id
)
)
}
}
Session 343 warns about exactly this pattern:
✅ VERIFIED (transcript, WWDC26 343) — "Collection annotations help me avoid the overhead of attaching an annotation to every single row. Instead, the system fetches identifiers lazily, as it needs them. Collection annotations also let Siri discover entities that have been selected and scrolled off screen. Per row annotations disappear as soon as the view leaves the view hierarchy."
SwiftUI recycles rows. When a row scrolls out of the hierarchy its annotation goes with it. A user who selects something, scrolls away, and then says "send that one" has selected an entity the system can no longer see — and the failure is Siri being confused, not an error in your logs.
The correct form for lists:
// ✅ VERIFIED (Apple code sample, WWDC26 343 @ 16:27) — the CORRECT form for lists
struct PlaylistDetailView: View {
var body: some View {
List {
ForEach(playlist.tracks) { track in
PlaylistTrackRow(track: track)
}
}
.appEntityIdentifier(forSelectionType: GeneratedTrack.ID.self) { trackID in
EntityIdentifier(for: SongEntity.self, identifier: trackID)
}
}
}
240 shows the simple form; 343 shows the correct form for lists where selection matters. Both are Apple code samples. Prefer 343's. Full treatment in guide 03.
14.4 ⚠️ TransientAppEntity silently forecloses three system integrations¶
✅ VERIFIED (transcript, WWDC26 343) — "Note, that with the three entity annotation APIs I'm describing, you can't use
TransientAppEntity. Transient entities are temporary model objects, so they don't have persistent identifiers."
The three surfaces are notifications (UNMutableNotificationContent.appEntityIdentifiers), Now
Playing (MusicContent.appEntityIdentifiers), and AlarmKit
(AlarmConfiguration.alarm(…appEntityIdentifier:…)). ✅ VERIFIED (Apple code sample, 343 @
21:07).
Session 344 chooses TransientAppEntity for AttendeeEntity for excellent local reasons (§10.6).
The consequence is that a CometCal attendee can never be an annotation target on any of those
three surfaces. That is a live design constraint discovered a session apart from the decision that
causes it.
🔴 GAP: whether this is a compile error, a runtime assertion, or a silent no-op is not stated anywhere in our sources. What would resolve it: trying it. Safe default: treat it as silent. Decide transient-versus-persistent with the system-integration question explicitly on the table: "will this type ever need to appear in a notification, in Now Playing, or on an alarm?"
14.5 ⚠️ SyncableEntity is a promise nothing validates¶
Conforming to SyncableEntity with a locally-generated id and no SyncableEntityIdentifier is a
lie to the system that compiles cleanly. The protocol declares stability (§13.5); it does not
provide it. Nothing checks. The symptom is the exact failure the API exists to prevent — Siri
losing an entity across devices — appearing in an app that has "adopted" the fix.
Safe default: before conforming, answer in one sentence where the stable ID comes from. If the
answer is "the ID we already have", verify that it is generated centrally (server, CloudKit record
ID) and not per-device (Core Data row ID, UUID() at insert time).
14.6 ⚠️ LongRunningIntent without progress reporting¶
Progress is the liveness signal (§13.7), not a UI nicety. An intent that adopts LongRunningIntent
and never writes to progress.completedUnitCount gives the system no evidence it is alive. Apple
says the requirement exists "so the system knows it's still working and hasn't stalled."
🔴 GAP: the exact consequence — killed, throttled, or merely shown without progress — is not
stated. Safe default: set totalUnitCount before the loop and update completedUnitCount
inside it, exactly as the sample does, even for work whose length you have to estimate.
14.7 ⚠️ SnippetIntent state caching¶
The system may call a SnippetIntent multiple times, and perform() must re-fetch state each time
(§11.4). A snippet that caches renders stale state after the user interacts with its own controls —
they tap a toggle, the toggle does not move, and nothing errors.
14.8 ⚠️ Raw internal errors reaching end users¶
Community-reported, not Apple-acknowledged. Forum thread 835903 records
TypedValueToContentGraphResolutionErrorDomain error 4 leaking to end users through the Siri
UI. Our research pass looked for first-party documentation of that behaviour and found none.
The machinery to prevent leakage does exist, and it is documented:
AppIntentErrorand its subtypes (PermissionRequired,Unrecoverable,UserActionRequired)AppIntentError.init(description:)— construct with a localized description (new)CustomAppIntentErrorConvertibleandCustomLocalizedStringResourceConvertible— map your domain errors to something presentable
✅ VERIFIED (docs) for the symbol names, from the framework's Errors topic — and ✅
SDK-verified for the load-bearing ones (AppIntents-27.0-macos.swiftinterface:3305-3340):
AppIntentError.init(description:), init(predefinedError:description:) and two
init(wrapping:) overloads are all anyAppleOS 27.0, as is protocol
CustomAppIntentErrorConvertible { var appIntentError: AppIntentError { get } };
UserActionRequired's members are .signin, .accountSetup and .confirmation.
The rule that follows: never let a raw Error escape perform(). Conform your error type to
CustomAppIntentErrorConvertible / CustomLocalizedStringResourceConvertible so that whatever
Siri surfaces is a string you wrote. Given thread 835903, assume anything that escapes is
user-visible.
14.9 The one that is not silent — the schema co-requisite build error¶
Worth ending on, because it is the counter-example. Adopting .messages.sendMessage without
.messages.draftMessage fails the build with a Fix-It (§3.3). Apple chose a loud failure for a
case that would otherwise have been a silent partial experience:
✅ VERIFIED (transcript, WWDC26 240) — "instead of failing silently at runtime, the build system surfaces this early."
That is the design instinct the rest of this section wishes had been applied more widely.
15. Testing: the four-stage ladder¶
Session 240 gives an ordered methodology that we have not seen written down anywhere else, and it is genuinely good. Its value is as a debugging funnel: each stage isolates one class of failure, so you always know which layer you are arguing with.
| Stage | Tool | What it validates — verbatim |
|---|---|---|
| 1 | AppIntentsTesting |
"lets you exercise your intents entirely in isolation. No Siri involved. … the fastest and most reliable way to validate your business logic early in development." |
| 2 | Shortcuts app | "This is where you validate the shape of your intent. Not just what it does, but how it's configured and exposed." |
| 3 | Spotlight | "where you validate your content integration, ensuring your entities are indexed correctly, discoverable, and linkable. This helps you confirm that Siri can find the right data before it ever tries to act on it." |
| 4 | Siri | "Natural language, entity resolution, on-screen context, and cross-app workflows." |
✅ VERIFIED (transcript, WWDC26 240) for every quoted cell.
AppIntentsTesting is a framework (/documentation/AppIntentsTesting) that lets you invoke
intents with test parameters and assert on results like ordinary unit tests, with no Siri involved.
✅ VERIFIED (docs) — the page exists.
Debug at the lowest stage that reproduces the failure. Most "Siri doesn't work" reports are stage-2 or stage-3 problems misdiagnosed as stage-4:
| Symptom | Most likely stage | Why |
|---|---|---|
| Intent does the wrong thing | 1 | business logic; Siri is not involved |
| Intent does not appear in Shortcuts | 2 | configuration/exposure, not language |
| Siri says it cannot find the thing | 3 | indexing or query protocol (§10) |
| Siri finds it but will not act | — | schema tier (§2, §6) — no amount of testing fixes this |
| "Remove the X" appears to work but doesn't | 1 | valueState (§14.1) — and stage 1 will catch it |
That fourth row is the one the ladder cannot help with, and it is why §5 and §6 exist: if the capability is not in the enumeration, no stage of testing will produce it.
A concrete stage-3 verification worth copying, from session 344: create an entity in the app
("Lunar Orbit Log"), swipe down to Spotlight, and search for it — "and there it is with the
calendar icon and the title." ✅ VERIFIED (transcript, 344). That single check validates
IndexedEntity conformance, the index call, and your DisplayRepresentation in one motion.
⚠️ A stage-3 obligation that is easy to miss. Conformance is not donation:
✅ VERIFIED (transcript, WWDC26 344) — "There's one more piece that's easy to miss.
IndexedEntitydefines the shape of my indexed content, but entities still need to be donated."
The rule: "Anytime calendars, or any indexed entity for that matter, are changed, the index needs
to be updated." ✅ VERIFIED (transcript, 344) — index on create, re-index on update, delete
on removal. An app that conforms to IndexedEntity and never calls indexAppEntities passes
compilation, passes stage 1, passes stage 2, and fails stage 3 with an empty Spotlight and no
error. Guide 04 covers the index lifecycle in full.
16. Gap register¶
Everything in this guide we could not verify, what would resolve it, and what to do meanwhile. A 🔴 GAP box in this series never contains a guess.
SDK-interface pass, 2026-07-29: every row below was checked against
AppIntents-26.5-macos.swiftinterface and AppIntents-27.0-macos.swiftinterface. Five rows
closed — G4, G5, G9, G15 and G17, marked ✅ resolved below with the answer inline. The interface
does not settle the remaining rows; where it narrows one, the row says so.
| # | Gap | What would resolve it | Safe default |
|---|---|---|---|
| G1 | Exact tier semantics. What capability does each of the three domain tiers actually confer? Inferred from grouping labels; never stated in prose. (§4) | A docs page or session statement defining tier reach; or empirical testing of an un-app-qualified Siri request against a Shortcuts-tier schema. Interface checked 2026-07-29 — encodes membership, not tier capability | If the feature must work through Siri without the user naming your app, require a primary-tier domain |
| G2 | Per-schema required-property tables. Verified for .photos.asset and .photos.openAsset only; ~180 schemas unenumerated. (§3.2) |
The per-schema doc pages under /documentation/appintents/appschema/<domain>intent/<name>. Interface checked 2026-07-29 — schemas are opaque string accessors there; not settled |
Do not design a data model around a schema you have not scaffolded in Xcode with <domain>_ |
| G3 | The schema co-requisite graph. Exactly one pair demonstrated: .messages.sendMessage ⇒ .messages.draftMessage. (§3.3) |
Adopting each schema in a scratch project and reading the build errors. Interface checked 2026-07-29 — co-requisites are not encoded there | Budget for "commit" verbs dragging in "prepare" siblings; plan adoption in pairs |
| G4 | ✅ Resolved (SDK, 2026-07-29). Same type: ValueRepresentation is an AppEntity-scoped typealias for IntentValueRepresentation, anyAppleOS 26.4 (AppIntents-27.0-macos.swiftinterface:2629-2634). §13.1 |
— | Port code freely between the spellings; the short one resolves in AppEntity scope |
| G5 | ✅ Resolved (SDK, 2026-07-29). .system.searchInApp is declared at anyAppleOS 27.0, underlying name SystemSearchInAppIntent; the deprecated .system.search carries the SDK message "Use .system.searchInApp instead" (:13774-13797). §8.2 |
— | On pre-27 SDKs, the deprecated .system.search is the same schema |
| G6 | AppEntityContext's full domain/situation inventory. Narrowed 2026-07-29: the macOS 27.0 interface spells only .audio(.nowPlaying); the session's .workout(activityType:) is absent from the macOS surface. (§13.2) |
The iOS interface, or the AppEntityContext docs page |
Rely on Xcode completion; do not invent a context path |
| G7 | IntentValueQuery input types — Apple's comment says "and other system types may be supported"; and the full AudioSearch criteria list. AudioSearch is absent from the macOS 27.0 interface entirely (checked 2026-07-29). (§10.5) |
The IntentValueQuery and AudioSearch docs pages, or the iOS interface |
Implement against the type the schema snippet gives you; switch exhaustively |
| G8 | .visualIntelligence input types. Docs say the query "receives Visual Intelligence types" without naming them. Interface checked 2026-07-29 — only the schema accessor is visible; not settled. (§5.2) |
The .visualIntelligence domain page symbol table |
Write the query against whatever the snippet scaffolds |
| G9 | ✅ Resolved (SDK, 2026-07-29), naming half. The type is IntentCancellationReason, anyAppleOS 26.4, members .timeout / .userCancelled (:3377-3389). No distinct member for resource reclamation — that mapping stays open. §13.7 |
— | switch non-exhaustively; treat unknown values as cleanup-and-exit |
| G10 | The background-GPU entitlement name for LongRunningIntent. Session 345 says "add GPU access to your app's entitlement" without naming it. The request side is now SDK-verified — LongRunningTaskOptions.requiresGPU (:3616) — but the entitlement name appears nowhere in the interface (checked 2026-07-29). (§13.7) |
Apple's entitlements documentation | continued-processing.gpu is a developer-reported existing background-GPU entitlement (forum corpus) — a lead, not the answer |
| G11 | Consequence of TransientAppEntity on annotation surfaces — compile error, runtime assertion, or silent no-op? (§14.4) |
Trying it | Assume silent; decide transient-vs-persistent with the system-integration question explicit |
| G12 | Consequence of LongRunningIntent without progress — killed, throttled, or merely progress-less? (§14.6) |
Testing a long intent that never updates progress | Always report progress |
| G13 | Donation throttle threshold, window, and scope. No number, no error, no API. (§14.2) | Apple documentation; none found | Donate only completed, user-initiated UI actions |
| G14 | Deprecated-schema removal timeline. Nothing in our sources says when or whether deprecated schemas stop compiling. (§7) | Release notes | Migrate at leisure; do not assume two more OS majors |
| G15 | ✅ Resolved (SDK, 2026-07-29). The other kinds are requestDisambiguation(among:dialog:) and requestConfirmation(for:dialog:), on IntentParameter since macOS 13 / iOS 16 (:4367-4369). §11.2 |
— | — |
| G16 | UndoableIntent and IntentModes semantics. Shapes now SDK-verified (§13 preamble): both anyAppleOS 26.0; UndoableIntent : SystemIntent with undoManager; IntentModes an OptionSet with .background / .foreground(...). Still in no session and no usage example. (§13) |
Their docs pages | Do not design around them from shape alone |
| G17 | ✅ Resolved (SDK, 2026-07-29). EntityOwnership is an OptionSet with .unknown / .shared / .public and no .private member (:8941-8947). "Private" is the implicit non-adoption default. §12.2 |
— | Use .unknown as the spelling for "cannot say" |
| G18 | Which domains get semantic (vs merely lexical) Spotlight search. Session 343 says it is "depending on the App Intents domain" and does not say which. (§10) | Apple documentation; none found | Do not promise semantic matching for an uncovered domain; verify empirically at testing stage 3 |
| G19 | Person-type relationships: .messages.messagePerson, .phone.phonePerson, and the system IntentPerson. (§5.1) |
The per-schema doc pages | Use the type the schema names; use IntentPerson for cross-app value transfer |
| G20 | Is custom AppEntity + Transferable on-screen hand-off supposed to work? Asked by a developer on thread 838329; Apple routed it to Feedback Assistant (FB23813341) without answering. (§2) |
An Apple answer; the radar | Assume schema-typed = actionable, non-schema = discoverable-only |
| G21 | How complete the §5 enumeration is — substantially closed 2026-07-29. The SDK census (§5.4) now verifies every row against the 27.0 macOS interface, correcting five domains. Residual softness: the counts are macOS-surface; an iOS interface could differ | The iOS interface, for the residual | Verify the specific domain you are adopting before planning around its inventory |
Corrections this guide applies¶
Two items from the series correction register are folded in above rather than left as open notes:
- C10.3 —
.system.searchInAppis a rename, not a new schema. TheUNVERIFIEDflag that previously sat on the spelling is downgraded on the strength of two independent renderings on session 343's page plusStringSearchCriteria's iOS 17.2 availability. §8.2. - C10.6 —
IntentParameter.valueStategets its own callout box, as required. §14.1.
And two cautions carried per the register, one since retired:
- Release-year labels are soft — the updates page and session 345 use different conventions and 345 says "2027 releases". Version-floor box, top of guide.
ValueRepresentationvsIntentValueRepresentation— resolved 2026-07-29 by the SDK pass: same type,AppEntity-scoped typealias. §13.1 and former G4.
17. Sources¶
Primary — SDK module interfaces (added 2026-07-29)¶
| File | What it is |
|---|---|
notes/sdk-interfaces/AppIntents-26.5-macos.swiftinterface |
The AppIntents Swift interface from the macOS 26.5 SDK (11,752 lines) |
notes/sdk-interfaces/AppIntents-27.0-macos.swiftinterface |
The AppIntents Swift interface from the Xcode 27 beta / macOS 27.0 SDK (16,826 lines) |
These are the declarations the compiler sees, and they outrank every other evidence class in this
guide. They supplied: the §5 domain census and its five corrections, the §8.2 searchInApp
resolution, every ✅ SDK-verified availability floor, and the closure of register entries G4, G5,
G9, G15 and G17. Two limits to keep in mind: they are the macOS surface, so an iOS-only symbol
(AudioSearch, the .workout context) is invisibly absent rather than demonstrated missing; and
Objective-C API does not appear in a Swift module interface at all.
Primary — Apple documentation¶
Read through the sosumi.ai markdown mirror on 2026-07-27 (developer.apple.com/documentation/X
→ sosumi.ai/documentation/X), recorded in notes/web/app-intents-siri-schemas.md.
| Page | Supplied |
|---|---|
/appintents/ |
Framework landing page and topic taxonomy |
/appintents/app-schema-domains |
The domain index — 23 domains in 3 tiers, plus the three macros |
/appintents/app-schema-domain-mail |
12 intents, 5 entities |
/appintents/app-schema-domain-files |
5 intents, 1 entity |
/appintents/app-schema-domain-photos |
28 intents, 3 entities, 4 enums |
/appintents/app-schema-domain-camera |
5 intents, 3 enums |
/appintents/app-schema-domain-browser |
13 intents, 5 entities, 1 enum |
/appintents/app-schema-domain-reader |
9 intents, 2 entities, 1 enum |
/appintents/app-schema-domain-journaling |
5 intents, 1 entity |
/appintents/app-schema-domain-books |
9 intents, 3 entities, "12" enums (SDK census: 10 — §5.4) |
/appintents/app-schema-domain-whiteboard |
7 intents, 2 entities, 2 enums |
/appintents/app-schema-domain-assistant |
1 intent (activate), Japan-only |
/appintents/app-schema-domain-system-and-in-app-search |
2 intents (open, search deprecated) |
/appintents/making-actions-and-content-discoverable-by-apple-intelligence |
The discovery/action framing; required-property lists; Fix-Its |
/appintents/providing-contextual-cues-to-apple-intelligence-and-siri |
"Schema application is optional but recommended"; annotation APIs |
/appintents/making-app-entities-available-in-spotlight |
IndexedEntity, indexAppEntities |
/appintents/snippetintent |
SnippetIntent declaration and iOS 26.0 availability |
/appintents/stringsearchcriteria |
StringSearchCriteria, iOS 17.2 / macOS 14.2, term, conformances |
/updates/appintents |
The 2026 API list |
/AppIntentsTesting |
The testing framework's existence |
Two URLs that failed and are worth knowing about: /appintents/making-onscreen-content-available-
to-siri-and-apple-intelligence returned 404 despite being cited by name in forum thread 838329
(the live equivalent appears to be the contextual-cues page), and /appintents/app-intent-domains
is not a real path — the correct one is app-schema-domains.
Primary — WWDC26 sessions¶
All fetched 2026-07-27 directly from developer.apple.com/videos/play/wwdc2026/<n>/, which
returns the full transcript and Apple's published code-sample block. Notes in
notes/transcripts/missing-sessions.md; raw transcripts in transcripts/wwdc2026-<n>.txt.
| Session | Title | Presenter | Sample app | Code block? |
|---|---|---|---|---|
| 240 | Build intelligent Siri experiences with App Schemas | Dan Niemeyer, Swift Intelligence Frameworks | UnicornChat | ✅ 5 samples |
| 343 | Explore advanced App Intents features for Siri and Apple Intelligence | Antonio Cancio, App Intents | CosmoTunes, UnicornChat, CometCal | ✅ 9 samples |
| 344 | Code-along: Make your app available to Siri | Justin Kang, Swift Intelligence Frameworks | CometCal | ❌ none — narration only |
| 345 | Discover new capabilities in the App Intents framework | Moe, App Intents | Landmarks Travel Tracking | ✅ 7 samples |
⚠️ Session 344 published no code-sample block. Every code listing in this guide attributed to
344 is 🟡 RECONSTRUCTED from narration and labelled as such. The facts asserted in 344's
narration — valueState's three states, the TransientAppEntity rule, EnumerableEntityQuery's
purpose, the system.open parameter name — are quoted directly and are reliable; the code shapes
around them are ours.
Developer Forums¶
Thread numbers, with who answered:
| Thread | Subject | Answered by |
|---|---|---|
| 838329 | On-screen image hand-off; the .files.file recipe |
Another developer (J0hn, marked recommended). Apple's DTS engineer deflected to Feedback Assistant, FB23813341 |
| 837249 | On-screen awareness without a schema (hiking/cycling app) | Nobody. 0 replies, ~205 views |
| 829586 | App Intents vs App Schemas | Community, 14 replies, ~1k views. FB23018652, characterised by DTS as an enhancement request |
| 835903 | Raw internal error (TypedValueToContentGraphResolutionErrorDomain error 4) surfacing through Siri |
Community report; no Apple acknowledgement located |
| 836760 | Foundation Models tied to Siri enablement | Apple Frameworks Engineer — confirmed it is a bug |
| 835211 | SystemLanguageModel.default.availability tied to Siri toggle |
Nobody; corroborates 836760 |
| 775988 | DynamicOptionsProvider + search composition |
Community; predates the 2026 work |
The pattern is itself a finding. Across this cluster there is one substantive Apple answer (836760, and it was about Foundation Models availability rather than App Intents), one deflection to Feedback Assistant, and otherwise silence. The single most useful technical answer in the cluster came from another developer. Weight community findings accordingly — not because they are strong evidence, but because in this area they are frequently the only evidence.
Corpus files behind this guide¶
notes/web/app-intents-siri-schemas.md(1,652 lines) — the domain enumeration, the docs quotations, the forum analysisnotes/transcripts/missing-sessions.md(3,216 lines) — sessions 240, 343, 344, 345 with Apple's published code samples reproducednotes/forums/forum-pain-points.md(1,538 lines) — the thread inventory and the undocumented error/limit tabletranscripts/wwdc2026-{240,343,344,345}.txt— the raw session prose
How to re-verify any row of §5¶
The fastest route, and it takes about thirty seconds per domain:
grep '<Domain>Intent {' notes/sdk-interfaces/AppIntents-27.0-macos.swiftinterface— the SDK census route this guide's 2026-07-29 pass used; each domain's marker-protocol extension lists every leaf accessor.https://sosumi.ai/documentation/appintents/app-schema-domain-<name>— for the domain's inventory. Note the URL slugs that do not match the dot-syntax:journaling(notjournal),system-and-in-app-search(notsystem).https://sosumi.ai/documentation/appintents/appschema/<domain>intent/<schemaname>— for an individual schema's page.- In Xcode 27, type
<domain>_and read the completion list. This is the authoritative answer and it is the only one that reflects the SDK you are actually building against.
Step 3 beats steps 1 and 2. Use this guide to know what to look for; use Xcode to know what is there.
Compiled 2026-07-28 from sources fetched 2026-07-27; re-verified 2026-07-29 against the macOS
26.5 and 27.0-beta SDK module interfaces in notes/sdk-interfaces/. Every schema identifier, API
name and quotation above traces to an SDK interface declaration, an Apple documentation page, an
Apple-published session code sample, or an Apple session transcript — or is explicitly marked 🟡
RECONSTRUCTED or 🔴 GAP. The §5 census, all ✅ SDK-verified markers, and the closure of register
entries G4, G5, G9, G15 and G17 date from the 2026-07-29 pass. Where the notes and the brief
disagreed, the notes won; where the interface and anything else disagreed, the interface won.
-
Apple, Creating your first app intent, §“Register dependencies to other parts of your code,” constructs one
NavigationModel, stores it on the app, registers it withAppDependencyManager.shared.add(dependency:), and later resolves it with@Dependency. The same-instance behavior is also stated in the authoritative WWDC26 session 344 transcript, lines 53–56. ↩↩ -
Apple,
FileEntityIdentifieranddraft(identifier:), document draft identifiers for documents that are not yet materialized on disk and therefore have no file URL. The saved-file factory is separately documented asfile(url:). ↩