In the iOS 27 release, Siri takes a real step forward. It can now reach into the actual content inside your app, take actions on your behalf, and understand what is on screen right now. The way you plug into all of that as a developer is the App Intents framework, and the piece that makes it click is App Schemas.
This guide walks through the core ideas from Apple's session Build intelligent Siri experiences and turns them into something you can act on.
If App Intents is completely new to you, it is worth watching "Get to know App Intents" first, since everything below builds on that foundation.
The big picture: three new powers for Siri
There are three ways Siri gets more capable this year, and it helps to hold all three in your head before diving into code.
First, Siri can access your app's entities, meaning the real content inside your app. Someone can ask "When and where is my next meeting?" and Siri answers directly, because it understands what a meeting is, which one is relevant, and which properties to return.
Second, Siri can take actions using your app's intents. A request like "Send my latest report to Mary" works because your intent describes the action, its parameters, and when it is safe to run. Siri handles the language understanding; your app just does the work.
Third, Siri can understand on-screen context. When you annotate your views with entities, someone can say "Explain this text" or "Forward the last one" and Siri knows exactly what content they mean.
Everything below is built on one central concept, so let us start there.
App Entities: describing the content you already have
An App Entity is a structured description of the content inside your app. You are not building a new data model. You are describing content you already have in a way the system can reason about.
Think about the nouns in your app. A calendar app has events. A mail app has messages. A photos app has photos and albums. UnicornChat has Contacts, Conversations, and Messages. Each of these is an App Entity.
An entity describes three things: what the thing is, how it is identified, and which properties matter (a title, a date, some text). That is it.
Modeling an entity is step one, but on its own it is not enough for Siri to find it or talk about it. For that, your entity needs to conform to an App Schema.
App Schemas: giving Siri a shared vocabulary
An App Schema gives Siri a predefined understanding of a common concept, such as a message, a contact, or a document. When your entity conforms to a schema, Siri already knows how to reason about it. Instead of treating your app as a black box, it understands that a UnicornChat message is a message.
This is the key mental shift. You do not teach Siri your app's vocabulary or define training phrases. You declare your data against a shape Siri already knows, and the language understanding comes for free. Because schemas are system-defined, your integration automatically benefits as Siri improves and expands to new languages and dialects.
Here is how UnicornChat contributes a message's text content to Apple Intelligence by conforming to the messages message schema:
// 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?
}
That single schema: .messages.message is what lets Siri understand a request like "Show my last message from Flare."
Entity resolution: turning words into real objects
Once your content is modeled, the next question is how Siri finds the right one. That process is called entity resolution. When someone says "Open UnicornChat with Glow," Siri resolves that "Glow" refers to a specific contact, finds the match, and fills in the entity with its properties.
But people do not always name things exactly. They describe them. Someone might say "the best windsurfing in Carmel," which is a meaning, not a text match. To support that, Siri needs semantic search, and there are two ways to power resolution.
Option 1: IndexedEntity (the best experience)
The primary path is adopting IndexedEntity. When you do, your entities are added to the system's semantic index in Spotlight. That lets Siri match on meaning rather than exact text, understand relationships between entities, and answer questions over your content.
For example, "Show the messages with Flare about movies" is not a string match. Siri can find messages that reference movie titles because it is running a semantic query over UnicornChat's indexed messages.
Notice the indexingKey in the earlier code sample. That is how you tell Spotlight which properties, like the message body, should be searchable. IndexedEntity gives you semantic matching, fewer follow-up questions, and the most natural language understanding.
Option 2: EntityStringQuery (when indexing is not feasible)
Not everything can be indexed ahead of time. Your dataset might be huge, live on a server, or change too frequently. In those cases you use EntityStringQuery. Siri hands you the person's raw input string, and your app is responsible for finding and returning the matches.
// 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)
}
}
You lose semantic understanding here, but you gain full control over how you search. The rule of thumb: index when you can, use a string query when you cannot.
App Intents and action schemas: letting Siri do things
Entities on their own are just information. Things get interesting when you combine them with actions, and actions come from App Intents.
When you define an App Intent, that action can appear across the system: in Shortcuts, Spotlight, Widgets, and more. People can discover and trigger it even without Siri. You describe what the action does, define its parameters, and implement the behavior. The system handles surfacing and suggesting it.
To bring an action to Siri specifically, you adopt an action schema. Just as entities use schemas to be understood, actions use schemas to become executable by Siri. Think of a schema as a specialized App Intent, shaped so Siri knows how to process it. The schema defines the kind of action, the structure Siri expects, and how it maps to natural language. That is what lets Siri confidently handle "Send a message to Mary" or "Play my focus playlist."
Domains: grouping schemas into complete experiences
A single schema defines a single action, but apps usually need a set of them. That is why schemas are grouped into domains such as mail, photos, and messages. A domain is a category of contract between your app and Siri. When you adopt a domain, you implement its predefined schemas, map them to your app's functionality, and Siri immediately knows how to talk about your app in that category.
In UnicornChat, sending a message means adopting the sendMessage schema from the messages domain. In Xcode you start typing the schema name, autocomplete shows the available schemas grouped by domain, and you pick the one you want. Your job is then to map the schema's parameters (the recipient, the message content) onto your existing messaging flow: process the parameters, pass them into your send logic, and return the sent message back to the system as an entity.
The payoff looks like this in practice. You say "Send a message to Glow in UnicornChat, saying 'What movies do you recommend?'" Siri resolves Glow through your entity query, invokes your intent, and sends the message, all without opening the app.
Working across apps: on-screen awareness and content transfer
Many real requests span multiple apps. "Email my wife this reply from Bubbles" starts in one app and finishes in another. That combines two capabilities: understanding what the person is looking at, and moving that content elsewhere.
On-screen awareness
To let Siri understand references like "this message" or "that conversation," you connect what is visible to your entities. There are two APIs, for two situations.
Use UserActivity when there is one primary thing on screen, like a single document or a compose view. Use view annotations when several meaningful items are visible at once, like rows in a conversation. Here is the view annotation approach in UnicornChat, attaching each message row to its entity:
// Working across apps - View annotations
List {
ForEach(messages) { message in
MessageRow(message: message)
.appEntityIdentifier(
EntityIdentifier(
for: MessageEntity.self,
identifier: message.id
)
)
}
}
With that in place, someone can say "Edit this message" or "Forward the last one" and Siri resolves the entity straight from the view.
Exporting content to another app
Content transfer is what lets other apps act on your entities. You enable it by conforming your entity to Transferable and providing an IntentValueRepresentation. UnicornChat exports a ContactEntity as a system IntentPerson, which powers requests like "Call this contact." Your app does not need to know what happens next; it just describes its content accurately.
// Working across apps - Exporting content to another app
extension ContactEntity: Transferable {
static var transferRepresentation: some TransferRepresentation {
IntentValueRepresentation(
exporting: \.person
)
}
}
Receiving content: resolve or import
When content comes into your app, it either refers to something that already exists or represents something new. You decide which path to take.
If the incoming content should match an existing entity, use IntentValueQuery. This is conceptually like an entity query, but scoped to an intent parameter. Here UnicornChat receives an IntentPerson from another app and matches it to an existing contact:
// 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)
}
}
If the incoming content should create something new, add an importing closure to your IntentValueRepresentation. This converts the incoming value into a brand new entity, such as creating a new unicorn from an IntentPerson. Your app stays in control of how that content is stored.
// 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
})
}
}
Many apps use both: resolve when the content already exists, import when it does not.
Best practices and tooling
A few things separate a working integration from a great one.
Some Siri scenarios need more than one schema. If you adopt sendMessage, Xcode may raise a build error telling you that you also need draftMessage, because a complete send flow requires a way to draft and confirm. This is a design hint delivered at build time rather than a silent runtime failure. Xcode even offers a fix-it that generates a stub adoption for you to fill in. If your app mutates UI state in an intent, remember to run that work on the main actor.
When it comes to testing, work outward in layers. Start with AppIntentsTesting, a framework that lets you exercise your intents in isolation with no Siri involved, so you can validate business logic fast. Then use the Shortcuts app to inspect how your intent's parameters are shaped and exposed. Next check Spotlight to confirm your entities are indexed, discoverable, and linkable. Finally test end to end with Siri, where natural language, entity resolution, on-screen context, and cross-app workflows all come together.
Top comments (0)