# App Intents and Siri Shortcuts: Advanced iOS Automation 2026 > Complete guide to App Intents and Siri Shortcuts for iOS 18+. Build custom Siri actions, integrate Apple Intelligence, and automate your Swift app in 2026. - Published: 2026-03-08 - Updated: 2026-03-28 - Author: SharpSkill - Tags: app-intents, siri-shortcuts, ios, swift, apple-intelligence - Reading time: 15 min --- In 2026, with Apple Intelligence and the App Intents framework, iOS apps enter an era where user intent matters more than UI. Apps that don't expose intents become invisible in an AI-first OS. App Intents form the foundation enabling Siri, Spotlight, widgets, and the Action Button to interact with app functionality. > **What this article covers** > > This article presents the complete creation of App Intents and Siri Shortcuts for iOS 18+, from fundamental concepts through Apple Intelligence integration and App Intent Domains. ## Understanding the App Intents Framework The App Intents framework, introduced with iOS 16, modernizes intent construction in Swift by replacing the older SiriKit Intents framework. This declarative architecture enables creating system-discoverable actions: Spotlight, Shortcuts app, Siri, and the Action Button. ```swift // TaskIntent.swift 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 func perform() async throws -> some IntentResult & ReturnsValue { // 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, allowing Siri to request missing values. The `perform()` method executes business logic and returns a typed result. ## Defining App Entities for Data App Entities represent the "nouns" of the application: objects on which intents operate. They enable Siri to understand and manipulate app data. ```swift // TaskEntity.swift 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 } } // Query for searching and filtering entities struct TaskEntityQuery: EntityQuery { // Search by identifiers func entities(for identifiers: [UUID]) async throws -> [TaskEntity] { TaskService.shared.fetchTasks() .filter { identifiers.contains($0.id) } .map { TaskEntity(task: $0) } } // Suggestions displayed in the interface func suggestedEntities() async throws -> [TaskEntity] { TaskService.shared.fetchTasks() .filter { !$0.isCompleted } .prefix(5) .map { TaskEntity(task: $0) } } } ``` The `EntityQuery` defines how the system searches and suggests entities. The `entities(for:)` and `suggestedEntities()` methods feed Siri and Shortcuts interfaces. > **App Enums vs App Entities** > > Use `AppEnum` for types with a fixed set of values (priority, status), and `AppEntity` for dynamic user-created types (tasks, notes, contacts). ## Creating App Enums for Fixed Values App Enums expose enumerated types to the system, enabling Siri to offer contextual choices. ```swift // TaskPriority.swift 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") ) ] } // Enum for task status enum TaskStatus: String, AppEnum, Codable { case pending case inProgress case completed static var typeDisplayRepresentation: TypeDisplayRepresentation = "Status" static var caseDisplayRepresentations: [TaskStatus: DisplayRepresentation] = [ .pending: "Pending", .inProgress: "In Progress", .completed: "Completed" ] } ``` Visual representations (SF Symbols icons) enrich display in Shortcuts and Siri suggestions. ## Implementing AppShortcutsProvider The `AppShortcutsProvider` exposes App Shortcuts to the system, making them immediately available without user configuration. These shortcuts appear in Spotlight, Siri, and the Action Button. ```swift // ShortcutsProvider.swift 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 to list tasks AppShortcut( intent: ListTasksIntent(), phrases: [ "Show my tasks in \(.applicationName)", "Display tasks from \(.applicationName)", "What are my tasks \(.applicationName)" ], shortTitle: "My Tasks", systemImageName: "list.bullet" ) // Shortcut with dynamic parameter AppShortcut( intent: CompleteTaskIntent(), phrases: [ "Complete \(\.$taskName) in \(.applicationName)", "Mark \(\.$taskName) as done with \(.applicationName)" ], shortTitle: "Complete Task", systemImageName: "checkmark.circle" ) } } ``` Voice phrases must include the `\(.applicationName)` placeholder for Siri to identify the target app. Dynamic parameters like `\(\.$taskName)` enable contextual commands. > **10 Shortcuts Limit** > > An application can declare a maximum of 10 App Shortcuts. Prioritize the most frequent and useful actions for users. ## Intents with Complex Parameters Intents can accept complex parameters including entities and configuration options. ```swift // CompleteTaskIntent.swift import AppIntents struct CompleteTaskIntent: AppIntent { static var title: LocalizedStringResource = "Complete a task" static var description = IntentDescription( "Marks a task as completed." ) // Entity parameter with automatic search @Parameter(title: "Task") var task: TaskEntity // Optional parameter with date @Parameter(title: "Completion date") var completionDate: Date? // Siri dialog configuration static var parameterSummary: some ParameterSummary { Summary("Complete \(\.$task)") { \.$completionDate } } func perform() async throws -> some IntentResult & ProvidesDialog { // Update the task TaskService.shared.completeTask( id: task.id, completionDate: completionDate ?? Date() ) // Voice feedback for Siri return .result( dialog: "Task \(task.title) has been marked as completed." ) } } // Intent returning a list of entities struct ListTasksIntent: AppIntent { static var title: LocalizedStringResource = "List tasks" // Optional filter @Parameter(title: "Status", default: nil) var statusFilter: TaskStatus? @Parameter(title: "Priority", default: nil) var priorityFilter: TaskPriority? func perform() async throws -> some IntentResult & ReturnsValue<[TaskEntity]> { var tasks = TaskService.shared.fetchTasks() // Apply filters if let status = statusFilter { tasks = tasks.filter { switch status { case .completed: return $0.isCompleted case .pending, .inProgress: return !$0.isCompleted } } } if let priority = priorityFilter { tasks = tasks.filter { $0.priority == priority } } let entities = tasks.map { TaskEntity(task: $0) } return .result(value: entities) } } ``` The `ParameterSummary` defines how Siri presents the intent during voice execution, providing natural feedback. ## Apple Intelligence Integration iOS 18 introduces App Intent Domains, collections of APIs designed for specific functionality. These domains enable Apple Intelligence to understand and execute actions with increased accuracy. ```swift // BookmarkDomainIntents.swift import AppIntents // Conforming to Bookmarks domain for Apple Intelligence integration struct SaveBookmarkIntent: AppIntent { static var title: LocalizedStringResource = "Save a bookmark" // Automatically validated URL parameter @Parameter(title: "URL") var url: URL @Parameter(title: "Title", default: nil) var title: String? @Parameter(title: "Folder", default: nil) var folder: BookmarkFolderEntity? // Open app if necessary static var openAppWhenRun: Bool = false func perform() async throws -> some IntentResult & ProvidesDialog { let bookmark = BookmarkService.shared.save( url: url, title: title, folder: folder?.id ) return .result( dialog: "Bookmark saved: \(bookmark.title)" ) } } // Intent with screen content awareness (iOS 18.4+) struct AnalyzeScreenContentIntent: AppIntent { static var title: LocalizedStringResource = "Analyze content" // Screen context access via Apple Intelligence @Parameter(title: "Context") var screenContext: String? func perform() async throws -> some IntentResult & ProvidesDialog { guard let context = screenContext else { return .result(dialog: "No content to analyze.") } // Process content extracted by Apple Intelligence let analysis = ContentAnalyzer.analyze(context) return .result(dialog: analysis.summary) } } ``` Predefined domains (Books, Camera, Spreadsheets) enable Siri to respond to requests accurately thanks to models trained on these specific tasks. ## Siri Actions with User Confirmation For sensitive actions, the system can request confirmation before execution. ```swift // DeleteTaskIntent.swift import AppIntents struct DeleteTaskIntent: AppIntent { static var title: LocalizedStringResource = "Delete a task" @Parameter(title: "Task") var task: TaskEntity // Requires user confirmation static var isDiscoverable: Bool = true func perform() async throws -> some IntentResult & ProvidesDialog { // Request confirmation via dialog try await requestConfirmation( result: .result( dialog: "Are you sure you want to delete \(task.title)?" ) ) // Delete after confirmation TaskService.shared.deleteTask(id: task.id) return .result( dialog: "Task \(task.title) has been deleted." ) } } // Intent with multiple dialog steps struct ScheduleTaskIntent: AppIntent { static var title: LocalizedStringResource = "Schedule a task" @Parameter(title: "Task") var task: TaskEntity @Parameter(title: "Date") var scheduledDate: Date @Parameter(title: "Reminder", default: true) var setReminder: Bool static var parameterSummary: some ParameterSummary { When(\.$setReminder, .equalTo, true) { Summary("Schedule \(\.$task) for \(\.$scheduledDate) with reminder") } otherwise: { Summary("Schedule \(\.$task) for \(\.$scheduledDate)") } } func perform() async throws -> some IntentResult & ProvidesDialog { TaskService.shared.schedule( taskId: task.id, date: scheduledDate, reminder: setReminder ) let dateFormatter = DateFormatter() dateFormatter.dateStyle = .medium dateFormatter.timeStyle = .short let formattedDate = dateFormatter.string(from: scheduledDate) return .result( dialog: "Task scheduled for \(formattedDate)." ) } } ``` The `requestConfirmation` method pauses execution until user validation, protecting against accidental actions. ## Intents in Interactive Widgets App Intents integrate naturally with WidgetKit to create iOS 17+ interactive widgets. ```swift // TaskWidgetIntents.swift 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 directly connect interaction to the App Intent, without opening the application. ## Action Button Configuration The Action Button on iPhone 15 Pro and later can trigger App Shortcuts directly. ```swift // ActionButtonIntent.swift import AppIntents // Action Button optimized intent struct QuickCaptureIntent: AppIntent { static var title: LocalizedStringResource = "Quick capture" static var description = IntentDescription( "Quickly creates a task with a title." ) // Opens app for input static var openAppWhenRun: Bool = true func perform() async throws -> some IntentResult & OpensIntent { // Notification to open quick capture screen NotificationCenter.default.post( name: .quickCaptureTriggered, object: nil ) return .result(opensIntent: ShowQuickCaptureViewIntent()) } } // Declare in AppShortcutsProvider for Action Button extension TaskAppShortcutsProvider { @AppShortcutsBuilder static var appShortcuts: [AppShortcut] { // ... other shortcuts AppShortcut( intent: QuickCaptureIntent(), phrases: [ "Quick capture \(.applicationName)", "Quick note \(.applicationName)" ], shortTitle: "Capture", systemImageName: "bolt.circle" ) } } ``` Users can configure the Action Button to trigger this shortcut via Settings > Action Button. > **iOS 18+ Macros** > > iOS 18 introduces `@DeferredProperty` and `@ComputedProperty` macros to reduce boilerplate. App Intents can also reside in Swift Packages for cross-platform reuse. ## Intents in Swift Packages App Intents can be defined in Swift Packages for sharing between iOS, macOS, and watchOS. ```swift // Package.swift // swift-tools-version: 5.9 import PackageDescription let package = Package( name: "TaskIntents", platforms: [ .iOS(.v17), .macOS(.v14), .watchOS(.v10) ], products: [ .library(name: "TaskIntents", targets: ["TaskIntents"]) ], dependencies: [], targets: [ .target( name: "TaskIntents", dependencies: [] ) ] ) // Sources/TaskIntents/SharedIntents.swift import AppIntents // Cross-platform shared intent public struct SharedCreateTaskIntent: AppIntent { public static var title: LocalizedStringResource = "Create a task" @Parameter(title: "Title") public var taskTitle: String public init() {} public func perform() async throws -> some IntentResult { // Shared logic await TaskRepository.shared.create(title: taskTitle) return .result() } } // Platform-specific extension in app #if os(iOS) extension SharedCreateTaskIntent { // iOS-specific behavior static var openAppWhenRun: Bool = false } #endif ``` This architecture maintains a single codebase for intents while adapting behavior per platform. ## Testing App Intents App Intents can be unit tested like any Swift code. ```swift // TaskIntentTests.swift import XCTest import AppIntents @testable import TaskApp final class TaskIntentTests: XCTestCase { override func setUp() { super.setUp() // Reset service for isolated tests 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 testCompleteTaskIntent() async throws { // Given let task = TaskService.shared.createTask( title: "Task to complete", priority: .medium ) var intent = CompleteTaskIntent() intent.task = TaskEntity(task: task) // When _ = try await intent.perform() // Then let updatedTask = TaskService.shared.fetchTask(id: task.id) XCTAssertTrue(updatedTask?.isCompleted ?? false) } 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) } } ``` Tests verify intent behavior independently of the system interface. ## Best Practices and Optimizations Several patterns ensure performant App Intents and optimal user experience. ```swift // BestPractices.swift import AppIntents // 1. Localized phrases struct LocalizedTaskIntent: AppIntent { static var title: LocalizedStringResource = "task.create.title" static var description = IntentDescription( "task.create.description", categoryName: "task.category" ) @Parameter(title: "task.parameter.title") var taskTitle: String func perform() async throws -> some IntentResult { // ... return .result() } } // 2. Proper error handling enum TaskIntentError: Error, CustomLocalizedStringResourceConvertible { case taskNotFound case invalidInput case serviceUnavailable var localizedStringResource: LocalizedStringResource { switch self { case .taskNotFound: return "Task not found." case .invalidInput: return "Invalid data." case .serviceUnavailable: return "Service temporarily unavailable." } } } struct RobustTaskIntent: AppIntent { static var title: LocalizedStringResource = "Robust intent" @Parameter(title: "ID") var taskId: String func perform() async throws -> some IntentResult & ProvidesDialog { guard let uuid = UUID(uuidString: taskId) else { throw TaskIntentError.invalidInput } guard let task = TaskService.shared.fetchTask(id: uuid) else { throw TaskIntentError.taskNotFound } return .result(dialog: "Task found: \(task.title)") } } // 3. EntityQuery optimization struct OptimizedTaskQuery: EntityStringQuery { // Optimized text search func entities(matching string: String) async throws -> [TaskEntity] { // Service-side search with limit TaskService.shared.search(query: string, limit: 10) .map { TaskEntity(task: $0) } } // Limited suggestions for performance func suggestedEntities() async throws -> [TaskEntity] { TaskService.shared.fetchRecentTasks(limit: 5) .map { TaskEntity(task: $0) } } } // 4. Focus Filter for Focus modes struct TaskFocusFilter: SetFocusFilterIntent { static var title: LocalizedStringResource = "Filter tasks" @Parameter(title: "Show high priority only") var showHighPriorityOnly: Bool func perform() async throws -> some IntentResult { TaskService.shared.setFocusFilter(highPriorityOnly: showHighPriorityOnly) return .result() } } ``` These patterns ensure smooth system integration while maintaining optimal performance. ## Conclusion App Intents and Siri Shortcuts transform how users interact with iOS applications. In 2026, with Apple Intelligence, exposing intents is no longer optional but essential for delivering a modern and intuitive experience. ### App Intents iOS 18+ Checklist - ✅ 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 - ✅ Configure ParameterSummary for Siri feedback - ✅ Integrate Apple Intelligence domains if applicable - ✅ Test intents with unit tests - ✅ Localize titles and descriptions --- Source: SharpSkill (https://sharpskill.dev), tech interview preparation for your real stack. HTML version of this page: https://sharpskill.dev/en/blog/ios/app-intents-siri-shortcuts-advanced-ios-automation