App Schemas let developers describe their app's content and actions in terms Siri already understands, using structured entity types from predefined domains like Calendar. By conforming models to schematized entities and donating them to the Spotlight index, Siri can answer natural language questions and take actions on app data without any custom NLP.
⢠No training phrases or NLP required ā App Schemas map your app's data to types Siri already knows, enabling multi-turn conversations about your content out of the box.
⢠IndexedEntity + Spotlight donation enables semantic search, so users can ask about event notes, attendees, or locations in plain language and Siri finds the right data.
⢠Schema domains (Calendar, etc.) define composable entity relationships ā an EventEntity can embed CalendarEntity and AttendeeEntity ā letting Siri reason across your app's full data graph.
Demonstrates how to define a schematized EventEntity conforming to IndexedEntity using App Schemas, then donate it to the Spotlight index so Siri can answer questions about calendar events in natural language.
import AppIntents
import CoreSpotlight
import SwiftData
import Foundation
// MARK: - Schematized Calendar Entity
@AppEntity
struct CalendarEntity: IndexedEntity {
static let typeDisplayRepresentation = TypeDisplayRepresentation(name: "Calendar")
var id: UUID
@Property(title: "Title")
var title: String
var displayRepresentation: DisplayRepresentation {
DisplayRepresentation(
title: "\(title)",
image: .init(systemName: "calendar")
)
}
struct DefaultQuery: EntityQuery, EnumerableEntityQuery {
@Dependency var calendarManager: CalendarManager
func entities(for identifiers: [UUID]) async throws -> [CalendarEntity] {
calendarManager.calendars
.filter { identifiers.contains($0.id) }
.map { CalendarEntity(id: $0.id, title: $0.title) }
}
func allEntities() async throws -> [CalendarEntity] {
calendarManager.calendars
.map { CalendarEntity(id: $0.id, title: $0.title) }
}
}
init(id: UUID, title: String) {
self.id = id
self.title = title
}
}
// MARK: - Donating Entities to Spotlight
@MainActor
class CalendarManager {
var calendars: [CalendarModel] = []
private let searchableIndex = CSSearchableIndex(name: "com.example.cometcal")
func createCalendar(title: String) async throws -> CalendarModel {
let model = CalendarModel(id: UUID(), title: title)
calendars.append(model)
let entity = CalendarEntity(id: model.id, title: model.title)
try await searchableIndex.indexAppEntities([entity])
return model
}
func updateCalendar(_ model: CalendarModel) async throws {
if let idx = calendars.firstIndex(where: { $0.id == model.id }) {
calendars[idx] = model
}
let entity = CalendarEntity(id: model.id, title: model.title)
try await searchableIndex.indexAppEntities([entity])
}
func deleteCalendar(id: UUID) async throws {
calendars.removeAll { $0.id == id }
try await searchableIndex.deleteAppEntities(identifiedBy: [id], ofType: CalendarEntity.self)
}
}
// MARK: - Lightweight Data Model
struct CalendarModel: Identifiable {
var id: UUID
var title: String
}Entities must be actively donated via indexAppEntities on every create/update, and removed via deleteAppEntities on delete ā stale index entries will cause Siri to reference data that no longer exists. TransientAppEntity should be used for entities that exist only in context of a parent (e.g. attendees), not independently queryable items. EnumerableEntityQuery is required in addition to EntityQuery when Siri needs to present all options to the user (e.g. choosing a calendar).
Requires Apple Intelligence-capable device (iPhone 15 Pro or later, or any iPhone 16+). Apple Intelligence availability varies by region.
More iOS 27 APIs land every week.
Get notified when new capabilities are published ā no noise, just signal.