App Intents 2.0 and Siri Shortcuts: iOS 27 Automation Guide

Complete guide to App Intents 2.0 and Siri Shortcuts for iOS 27. Build streaming responses, multi-turn conversations, View Annotations, and integrate with Foundation Models.

App Intents and Siri Shortcuts for advanced iOS automation with Swift and Apple Intelligence

App Intents 2.0 and Siri Shortcuts represent the only pathway for third-party apps to integrate with Siri on iOS 27. Following WWDC 2026, Apple deprecated SiriKit and positioned App Intents as the mandatory framework for voice interaction, Spotlight discovery, and automation workflows.

What this article covers

This article presents the complete creation of App Intents and Siri Shortcuts for iOS 27, from fundamental concepts through streaming responses, multi-turn conversations, and View Annotations API.

Understanding App Intents 2.0 Framework

The App Intents framework, first introduced with iOS 16 and expanded to version 2.0 at WWDC 2026, is a Swift-native, declarative framework for building system-discoverable actions. iOS 27 brings four major additions: streaming responses for long-running operations, multi-turn conversational follow-ups, View Annotations for referencing on-screen elements, and App Schemas for semantic understanding without training phrases.

At WWDC 2026, Apple formally deprecated SiriKit and made App Intents the exclusive way Siri can interact with third-party apps. Existing SiriKit code continues to compile with deprecation warnings, but Apple has signaled a two-to-three-year support window before removal.

TaskIntent.swiftswift
import AppIntents

// An AppIntent represents an action users can perform
struct CreateTaskIntent: AppIntent {
    // Title displayed in Shortcuts and Siri
    static var title: LocalizedStringResource = "Create a task"

    // Description for accessibility and suggestions
    static var description = IntentDescription(
        "Creates a new task in the application."
    )

    // Parameter with automatic validation
    @Parameter(title: "Task title")
    var taskTitle: String

    // Optional parameter with default value
    @Parameter(title: "Priority", default: .medium)
    var priority: TaskPriority

    // Action execution with async/await
    func perform() async throws -> some IntentResult & ReturnsValue<TaskEntity> {
        // Create task via service
        let task = TaskService.shared.createTask(
            title: taskTitle,
            priority: priority
        )

        // Return created entity for chaining
        return .result(value: TaskEntity(task: task))
    }
}

The intent declares parameters using the @Parameter property wrapper, enabling Siri to request missing values conversationally. The perform() method executes business logic and returns a typed result. The type system itself serves as the schema that Siri AI discovers dynamically.

Streaming Responses for Long-Running Actions

App Intents 2.0 introduces streaming responses, allowing intents to report progress during execution rather than blocking until completion. This capability addresses scenarios like file uploads, data synchronization, or complex calculations where users need feedback.

StreamingExportIntent.swiftswift
import AppIntents

struct ExportDataIntent: AppIntent {
    static var title: LocalizedStringResource = "Export data"

    @Parameter(title: "Format")
    var format: ExportFormat

    // Streaming response for progress reporting
    func perform() async throws -> some IntentResult & ProvidesDialog {
        let totalItems = DataService.shared.itemCount
        var processed = 0

        // Stream progress updates to Siri
        for item in DataService.shared.allItems {
            await exportItem(item, format: format)
            processed += 1

            // Report progress at intervals
            if processed % 100 == 0 {
                await reportProgress(
                    "Exported (processed) of (totalItems) items..."
                )
            }
        }

        return .result(
            dialog: "Export complete. (totalItems) items saved as (format.rawValue)."
        )
    }
}

Streaming responses enable natural feedback during operations that take more than a few seconds, maintaining user engagement instead of leaving them waiting in silence.

Multi-Turn Conversational Follow-Ups

Multi-turn conversations allow Siri to ask clarifying questions and continue the interaction within a single session. This feature transforms App Intents from one-shot commands into conversational workflows.

ConversationalTaskIntent.swiftswift
import AppIntents

struct SmartTaskIntent: AppIntent {
    static var title: LocalizedStringResource = "Create smart task"

    @Parameter(title: "Task title")
    var taskTitle: String?

    @Parameter(title: "Priority")
    var priority: TaskPriority?

    @Parameter(title: "Due date")
    var dueDate: Date?

    // Multi-turn conversation flow
    func perform() async throws -> some IntentResult & ProvidesDialog {
        // Request missing parameters conversationally
        let title = try await taskTitle ?? requestValue(
            for: .$taskTitle,
            dialog: "What should the task be called?"
        )

        let taskPriority = try await priority ?? requestValue(
            for: .$priority,
            dialog: "What priority level?"
        )

        // Optional follow-up
        let date: Date?
        if try await requestConfirmation(
            result: .result(dialog: "Should this task have a due date?")
        ) {
            date = try await requestValue(
                for: .$dueDate,
                dialog: "When is it due?"
            )
        } else {
            date = nil
        }

        let task = TaskService.shared.createTask(
            title: title,
            priority: taskPriority,
            dueDate: date
        )

        return .result(
            dialog: "Created '(task.title)' with (taskPriority.rawValue) priority."
        )
    }
}

The requestValue(for:dialog:) method pauses execution until Siri receives the user's response, creating a natural back-and-forth exchange rather than requiring all parameters upfront.

View Annotations API for On-Screen References

The View Annotations API, new in iOS 27, enables users to reference UI elements directly in Siri commands using phrases like "this photo", "the third one", or "that message". This capability requires annotating SwiftUI views with semantic information.

AnnotatedPhotoView.swiftswift
import SwiftUI
import AppIntents

struct PhotoGridView: View {
    let photos: [Photo]

    var body: some View {
        LazyVGrid(columns: [GridItem(.adaptive(minimum: 100))]) {
            ForEach(photos) { photo in
                PhotoThumbnail(photo: photo)
                    // Annotate view for Siri reference
                    .appIntentAnnotation(
                        entity: PhotoEntity(photo: photo),
                        label: photo.title
                    )
            }
        }
    }
}

// Intent that accepts view-referenced entities
struct SharePhotoIntent: AppIntent {
    static var title: LocalizedStringResource = "Share photo"

    // Parameter resolved from on-screen annotation
    @Parameter(title: "Photo", supportsViewAnnotation: true)
    var photo: PhotoEntity

    func perform() async throws -> some IntentResult & ProvidesDialog {
        await ShareService.share(photo.id)
        return .result(dialog: "Photo shared.")
    }
}

When a user says "share this photo" while viewing the grid, Siri resolves "this" to the currently focused or last-tapped PhotoEntity through the annotation. This removes the friction of describing items verbally.

Device compatibility

View Annotations require iOS 27, but the App Intents layer itself works on every iOS 27 device including iPhone 11 and newer. Apple Intelligence features like on-screen awareness need iPhone 15 Pro or later.

Defining App Entities for Data

App Entities represent the "nouns" of the application: objects on which intents operate. They enable Siri to understand, search, and manipulate app data. Building effective entities is essential for SwiftUI state management patterns in intent-driven apps.

TaskEntity.swiftswift
import AppIntents

// Internal data model
struct Task: Identifiable, Codable {
    let id: UUID
    var title: String
    var priority: TaskPriority
    var isCompleted: Bool
    var dueDate: Date?
}

// Entity exposed to the system
struct TaskEntity: AppEntity {
    // Required unique identifier
    var id: UUID

    // Displayable properties
    var title: String
    var priority: TaskPriority
    var isCompleted: Bool

    // Display configuration in the system
    static var typeDisplayRepresentation: TypeDisplayRepresentation = "Task"

    // Visual representation of the instance
    var displayRepresentation: DisplayRepresentation {
        DisplayRepresentation(
            title: "(title)",
            subtitle: "(priority.rawValue)",
            image: .init(systemName: isCompleted ? "checkmark.circle.fill" : "circle")
        )
    }

    // Default query for searching entities
    static var defaultQuery = TaskEntityQuery()

    // Initializer from internal model
    init(task: Task) {
        self.id = task.id
        self.title = task.title
        self.priority = task.priority
        self.isCompleted = task.isCompleted
    }
}

// Optimized entity query with search
struct TaskEntityQuery: EntityStringQuery {
    // Text search with service-side filtering
    func entities(matching string: String) async throws -> [TaskEntity] {
        TaskService.shared.search(query: string, limit: 10)
            .map { TaskEntity(task: $0) }
    }

    // Search by identifiers
    func entities(for identifiers: [UUID]) async throws -> [TaskEntity] {
        TaskService.shared.fetchTasks()
            .filter { identifiers.contains($0.id) }
            .map { TaskEntity(task: $0) }
    }

    // Limited suggestions for performance
    func suggestedEntities() async throws -> [TaskEntity] {
        TaskService.shared.fetchRecentTasks(limit: 5)
            .map { TaskEntity(task: $0) }
    }
}

The EntityStringQuery protocol adds text search capability, enabling Siri to find entities by name. The suggestedEntities() method feeds Siri and Shortcuts interfaces with relevant options.

Use AppEnum for fixed values

Use AppEnum for types with a fixed set of values (priority, status), and AppEntity for dynamic user-created types (tasks, notes, contacts). Mixing them causes unnecessary complexity.

Creating App Enums for Fixed Values

App Enums expose enumerated types to the system, enabling Siri to offer contextual choices with visual representations.

TaskPriority.swiftswift
import AppIntents

// Enum exposed to the system
enum TaskPriority: String, AppEnum, Codable {
    case low
    case medium
    case high

    // Type name displayed
    static var typeDisplayRepresentation: TypeDisplayRepresentation = "Priority"

    // Representation of each case
    static var caseDisplayRepresentations: [TaskPriority: DisplayRepresentation] = [
        .low: DisplayRepresentation(
            title: "Low",
            image: .init(systemName: "arrow.down.circle")
        ),
        .medium: DisplayRepresentation(
            title: "Medium",
            image: .init(systemName: "minus.circle")
        ),
        .high: DisplayRepresentation(
            title: "High",
            image: .init(systemName: "exclamationmark.circle")
        )
    ]
}

SF Symbols icons enrich display in Shortcuts and Siri suggestions, making selection faster than verbal descriptions.

Ready to ace your iOS interviews?

Practice with our interactive simulators, flashcards, and technical tests.

Implementing AppShortcutsProvider

The AppShortcutsProvider exposes App Shortcuts to the system, making them immediately available without user configuration. These shortcuts appear in Spotlight, Siri, the Action Button, and the new Siri AI agentic workflows.

ShortcutsProvider.swiftswift
import AppIntents

// Provider declaring all app shortcuts
struct TaskAppShortcutsProvider: AppShortcutsProvider {
    // Maximum 10 shortcuts per application
    @AppShortcutsBuilder
    static var appShortcuts: [AppShortcut] {
        // Shortcut to create a task
        AppShortcut(
            intent: CreateTaskIntent(),
            phrases: [
                // The .applicationName placeholder is REQUIRED
                "Create a task with (.applicationName)",
                "New task in (.applicationName)",
                "Add a task to (.applicationName)"
            ],
            shortTitle: "Create Task",
            systemImageName: "plus.circle"
        )

        // Shortcut with dynamic parameter
        AppShortcut(
            intent: CompleteTaskIntent(),
            phrases: [
                "Complete (.$taskName) in (.applicationName)",
                "Mark (.$taskName) as done with (.applicationName)"
            ],
            shortTitle: "Complete Task",
            systemImageName: "checkmark.circle"
        )

        // Shortcut for smart conversational intent
        AppShortcut(
            intent: SmartTaskIntent(),
            phrases: [
                "Quick task (.applicationName)",
                "Remind me (.applicationName)"
            ],
            shortTitle: "Smart Task",
            systemImageName: "brain"
        )
    }
}

Voice phrases must include the (.applicationName) placeholder for Siri to identify the target app. Dynamic parameters like (.$taskName) enable contextual commands.

Intents in Interactive Widgets

App Intents integrate with WidgetKit to create interactive widgets. Understanding SwiftUI navigation patterns helps when building widget-to-app transitions.

TaskWidgetIntents.swiftswift
import AppIntents
import WidgetKit

// Widget-optimized intent (fast execution)
struct ToggleTaskFromWidgetIntent: AppIntent {
    static var title: LocalizedStringResource = "Toggle task"

    @Parameter(title: "Task ID")
    var taskID: String

    init() {}

    init(taskID: UUID) {
        self.taskID = taskID.uuidString
    }

    // No dialog for widgets
    func perform() async throws -> some IntentResult {
        guard let uuid = UUID(uuidString: taskID) else {
            return .result()
        }

        TaskService.shared.toggleCompletion(taskId: uuid)

        // Immediate widget refresh
        WidgetCenter.shared.reloadTimelines(ofKind: "TaskWidget")

        return .result()
    }
}

// Widget view with interactive button
import SwiftUI

struct TaskWidgetView: View {
    let task: Task

    var body: some View {
        Button(intent: ToggleTaskFromWidgetIntent(taskID: task.id)) {
            HStack {
                Image(systemName: task.isCompleted ? "checkmark.circle.fill" : "circle")
                    .foregroundStyle(task.isCompleted ? .green : .secondary)

                Text(task.title)
                    .strikethrough(task.isCompleted)
            }
            .padding()
        }
        .buttonStyle(.plain)
    }
}

Widgets use the Button(intent:) syntax to connect interaction directly to the App Intent without opening the application.

Foundation Models Framework Integration

iOS 27 introduces the Foundation Models framework, enabling on-device LLM inference. While Foundation Models has no direct Siri integration, apps can use it within App Intents to process natural language or generate responses.

AIAssistedIntent.swiftswift
import AppIntents
import FoundationModels

struct SummarizeNotesIntent: AppIntent {
    static var title: LocalizedStringResource = "Summarize notes"

    @Parameter(title: "Topic")
    var topic: String

    func perform() async throws -> some IntentResult & ProvidesDialog {
        let notes = NoteService.shared.fetchNotes(matching: topic)
        let content = notes.map(.content).joined(separator: "

")

        // Use Foundation Models for on-device summarization
        let model = LanguageModel.default
        let summary = try await model.generate(
            prompt: "Summarize these notes in 2 sentences: (content)",
            maxTokens: 100
        )

        return .result(dialog: IntentDialog(summary))
    }
}

Foundation Models runs at approximately 30 tokens per second on-device with zero cost per request. The framework requires iPhone 15 Pro or iPhone 16 and later for on-device processing.

Migrating from SiriKit to App Intents

For apps still using SiriKit, migration follows a structured process. Xcode 27 provides a "Convert to App Intent" tool for Widget configurations, but custom handlers require manual rewriting.

swift
// Before: SiriKit Intent Handler
class CreateTaskIntentHandler: NSObject, CreateTaskIntentHandling {
    func handle(intent: CreateTaskIntent) async -> CreateTaskIntentResponse {
        guard let title = intent.taskTitle else {
            return CreateTaskIntentResponse(code: .failure, userActivity: nil)
        }

        let task = TaskService.shared.createTask(title: title)
        return CreateTaskIntentResponse.success(task: task)
    }
}

// After: App Intent (iOS 27)
struct CreateTaskIntent: AppIntent {
    static var title: LocalizedStringResource = "Create a task"

    @Parameter(title: "Task title")
    var taskTitle: String

    func perform() async throws -> some IntentResult & ReturnsValue<TaskEntity> {
        let task = TaskService.shared.createTask(title: taskTitle)
        return .result(value: TaskEntity(task: task))
    }
}

The migration eliminates .intentdefinition files and Intent Extension targets. All intent logic moves into the main app target as Swift structs.

Testing App Intents

The AppIntentsTesting framework validates intents through the actual Siri, Shortcuts, and Spotlight infrastructure without mocks.

TaskIntentTests.swiftswift
import XCTest
import AppIntentsTesting
@testable import TaskApp

final class TaskIntentTests: XCTestCase {

    override func setUp() {
        super.setUp()
        TaskService.shared.reset()
    }

    func testCreateTaskIntent() async throws {
        // Given
        var intent = CreateTaskIntent()
        intent.taskTitle = "Test Task"
        intent.priority = .high

        // When
        let result = try await intent.perform()

        // Then
        let tasks = TaskService.shared.fetchTasks()
        XCTAssertEqual(tasks.count, 1)
        XCTAssertEqual(tasks.first?.title, "Test Task")
        XCTAssertEqual(tasks.first?.priority, .high)
    }

    func testEntityQuery() async throws {
        // Given
        let task1 = TaskService.shared.createTask(title: "Task 1", priority: .low)
        let task2 = TaskService.shared.createTask(title: "Task 2", priority: .high)

        let query = TaskEntityQuery()

        // When
        let entities = try await query.entities(for: [task1.id, task2.id])

        // Then
        XCTAssertEqual(entities.count, 2)
    }

    func testStreamingIntent() async throws {
        // Given
        var intent = ExportDataIntent()
        intent.format = .json

        // When
        let result = try await intent.perform()

        // Then: verify completion dialog
        XCTAssertNotNil(result)
    }
}

Tests verify intent behavior independently of the system interface, catching regressions before deployment.

Sources

App Intents iOS 27 Implementation Checklist

For apps targeting iOS 27, App Intents are no longer optional. Siri AI composes multi-step actions across apps, and apps without published intents are excluded from these agentic workflows.

  • Create AppIntents for the main app actions
  • Define AppEntities for manipulable data
  • Use AppEnum for enumerated types
  • Implement AppShortcutsProvider with voice phrases
  • Respect the 10 App Shortcuts maximum limit
  • Include (.applicationName) in all phrases
  • Add View Annotations to SwiftUI views for on-screen references
  • Implement streaming responses for long-running operations
  • Support multi-turn conversations for complex workflows
  • Migrate from SiriKit before the deprecation window closes
  • Test intents with AppIntentsTesting framework
  • Localize titles and descriptions

Start practicing!

Test your knowledge with our interview simulators and technical tests.

Daily challenge

Can you spot the bug in iOS?

One real snippet, one hidden bug, one attempt a day. No account needed to try.

Anthony Fillion-Maillet

Written by

Anthony Fillion-Maillet

Founder of SharpSkill

Full-stack developer for over 10 years. Runs SharpSkill and answers for everything published here.

Updated on August 20, 2026

Tags

#app-intents
#siri-shortcuts
#ios
#swift
#apple-intelligence

Share

Related articles