```html SwiftUI: How to Add Siri Shortcuts (iOS 17+, 2026)

How to Implement Siri Shortcuts in SwiftUI

iOS 17+ Xcode 16+ Advanced APIs: AppIntents Updated: May 11, 2026
TL;DR

Conform a struct to AppIntent, implement perform(), then register phrase triggers in an AppShortcutsProvider. Drop a ShortcutsLink anywhere in your SwiftUI view so users can add it to the Shortcuts app in one tap.

import AppIntents

struct LogWaterIntent: AppIntent {
    static let title: LocalizedStringResource = "Log Water"

    @Parameter(title: "Ounces", default: 8.0)
    var amount: Double

    func perform() async throws -> some IntentResult & ProvidesDialog {
        WaterStore.shared.log(ounces: amount)
        return .result(dialog: "Logged \(amount) oz. Stay hydrated!")
    }
}

// In your SwiftUI view:
ShortcutsLink()
    .shortcutsLinkStyle(.automaticOutline)

Full implementation

The modern AppIntents framework (iOS 16+, greatly improved in iOS 17) replaces the older SiriKit INIntent approach with pure Swift. You define intents as structs, expose them to Siri via AppShortcutsProvider — no Info.plist intent declarations needed — and surface a one-tap entry point with ShortcutsLink. iOS picks up your shortcut phrases automatically when the user first launches your app.

import AppIntents
import SwiftUI

// MARK: - 1. Define the Intent

struct LogWaterIntent: AppIntent {
    static let title: LocalizedStringResource = "Log Water Intake"
    static let description = IntentDescription(
        "Log your daily water intake directly from Siri or the Shortcuts app."
    )
    // Set false to run silently in the background
    static var openAppWhenRun: Bool = false

    @Parameter(title: "Amount (oz)", description: "Ounces of water to log.", default: 8.0)
    var amount: Double

    func perform() async throws -> some IntentResult & ReturnsValue<Double> & ProvidesDialog {
        try await WaterStore.shared.log(ounces: amount)
        let total = await WaterStore.shared.todayTotal()
        return .result(
            value: amount,
            dialog: "Logged \(amount) oz. You've had \(total) oz today — great work!"
        )
    }
}

// MARK: - 2. Register Phrases with AppShortcutsProvider

struct WaterShortcutsProvider: AppShortcutsProvider {
    /// Phrases must include \(.applicationName) at least once.
    static var appShortcuts: [AppShortcut] {
        AppShortcut(
            intent: LogWaterIntent(),
            phrases: [
                "Log water in \(.applicationName)",
                "Track my water intake in \(.applicationName)",
                "I drank water in \(.applicationName)"
            ],
            shortTitle: "Log Water",
            systemImageName: "drop.fill"
        )
    }

    // Called by the system when shortcuts need to be updated
    static var shortcutTileColor: ShortcutTileColor = .blue
}

// MARK: - 3. Persistent Store (actor-isolated for Swift concurrency)

actor WaterStore {
    static let shared = WaterStore()
    private let defaults = UserDefaults.standard
    private var todayKey: String {
        let fmt = Date.FormatStyle().month(.twoDigits).day(.twoDigits).year()
        return "waterLog_\(Date().formatted(fmt))"
    }

    func log(ounces: Double) throws {
        let current = defaults.double(forKey: todayKey)
        defaults.set(current + ounces, forKey: todayKey)
    }

    func todayTotal() -> Double {
        defaults.double(forKey: todayKey)
    }
}

// MARK: - 4. SwiftUI View with ShortcutsLink

struct ShortcutsOnboardingView: View {
    @State private var todayTotal: Double = 0

    var body: some View {
        VStack(spacing: 24) {
            Image(systemName: "drop.fill")
                .font(.system(size: 56))
                .foregroundStyle(.blue)

            Text("Siri Shortcuts")
                .font(.largeTitle.bold())

            Text("Ask Siri \"Log water in \(Bundle.main.displayName)\" or add it to the Shortcuts app below.")
                .multilineTextAlignment(.center)
                .foregroundStyle(.secondary)

            Text("Today: \(todayTotal, format: .number) oz")
                .font(.headline)
                .foregroundStyle(.blue)

            // ShortcutsLink opens the system sheet to add/manage your shortcuts
            ShortcutsLink()
                .shortcutsLinkStyle(.automaticOutline)
                .accessibilityLabel("Add Siri Shortcuts")
        }
        .padding(32)
        .task {
            todayTotal = await WaterStore.shared.todayTotal()
        }
    }
}

// MARK: - Bundle helper

extension Bundle {
    var displayName: String {
        object(forInfoDictionaryKey: "CFBundleDisplayName") as? String
            ?? object(forInfoDictionaryKey: "CFBundleName") as? String
            ?? "App"
    }
}

#Preview {
    ShortcutsOnboardingView()
}

How it works

  1. AppIntent conformanceLogWaterIntent is a plain Swift struct. The title static property is what Siri and the Shortcuts app display. Setting openAppWhenRun = false lets the intent run entirely in the background; set it to true if you need to deep-link into your UI.
  2. @Parameter property wrapper — Each parameter marked with @Parameter becomes a slot Siri can fill from the spoken phrase or prompt the user for. The default: value is used if the user doesn't specify one, enabling "Hey Siri, log water in MyApp" without any follow-up question.
  3. perform() return types — The return type some IntentResult & ReturnsValue<Double> & ProvidesDialog tells the system this intent gives back a typed value (usable in Shortcuts automation chains) and a spoken/displayed response via ProvidesDialog. You can mix and match these protocols as needed.
  4. AppShortcutsProvider — The WaterShortcutsProvider struct registers the shortcut automatically the first time the app launches — no user setup required. Phrases must embed \(.applicationName) so Siri can disambiguate between multiple apps.
  5. ShortcutsLink — This built-in SwiftUI view presents the system sheet where users can add, rename, or delete your shortcuts. The .shortcutsLinkStyle(.automaticOutline) adapts to light/dark mode automatically.

Variants

Intent with an enum parameter (e.g. drink type)

enum DrinkType: String, AppEnum {
    case water, coffee, tea, juice

    static let typeDisplayRepresentation: TypeDisplayRepresentation = "Drink Type"
    static let caseDisplayRepresentations: [DrinkType: DisplayRepresentation] = [
        .water:  "Water",
        .coffee: "Coffee",
        .tea:    "Tea",
        .juice:  "Juice"
    ]
}

struct LogDrinkIntent: AppIntent {
    static let title: LocalizedStringResource = "Log a Drink"
    static var openAppWhenRun: Bool = false

    @Parameter(title: "Drink Type", default: .water)
    var drink: DrinkType

    @Parameter(title: "Amount (oz)", default: 8.0)
    var amount: Double

    func perform() async throws -> some IntentResult & ProvidesDialog {
        // persist…
        return .result(dialog: "Logged \(amount) oz of \(drink.rawValue).")
    }
}

Donating a shortcut after in-app action (iOS 17+)

You can proactively surface your shortcut in Siri Suggestions by donating it at the moment a user performs an action in-app. Call AppShortcutsProvider.updateAppShortcutParameters() after saving new data so the system refreshes the intent's suggested phrases and parameters. In SwiftData or Observable model layers, trigger this in the .task or onChange modifier right after a successful write.

Common pitfalls

Prompt this with Claude Code

When using Soarias or Claude Code directly to implement this:

Implement Siri Shortcuts in SwiftUI for iOS 17+.
Use AppIntents: AppIntent, @Parameter, AppShortcutsProvider, and ShortcutsLink.
The intent should run in the background (openAppWhenRun = false).
Make it accessible (VoiceOver labels on ShortcutsLink).
Add a #Preview with realistic sample data.

In Soarias's Build phase, paste this into the prompt bar after your screens are scaffolded — Claude Code will wire the AppIntent into your existing SwiftData model and drop the ShortcutsLink into the correct settings or onboarding view automatically.

Related

FAQ

Does this work on iOS 16?

The AppIntents framework was introduced in iOS 16, but AppShortcutsProvider and ShortcutsLink both arrived with iOS 16.0 as well. However, iOS 17 added significant improvements — including richer entity queries, the updated @Parameter dialog system, and more stable phrase registration. This guide targets iOS 17+ to use those improvements. If you need iOS 16 support, the same code compiles but phrase auto-registration is less reliable; compensate by calling AppShortcutsProvider.updateAppShortcutParameters() on every cold launch.

Can I return rich UI (e.g. a custom snippet) from an AppIntent?

Yes — conform your return type to ShowsSnippetView and return a SwiftUI view from perform(). This renders a compact widget-like card inside the Shortcuts app and in Siri's response overlay. The view must be lightweight (no navigation, no state) since it runs in an extension process. Use the same AppIntentResult builder — just add & ShowsSnippetView to the return type and implement var body: some View directly on the result struct.

What's the UIKit / SiriKit equivalent?

The legacy approach uses INIntent / INExtension with an Intents Extension target and an IntentsUI Extension for custom UI. You had to declare intent classes in Info.plist and handle donation with INInteraction.donate(). AppIntents fully replaces this — Apple deprecated the INIntent-based Siri Shortcuts flow in favor of AppIntents starting iOS 16. For new apps, always use AppIntents; migration from INIntent to AppIntents is mechanical and well worth doing.

Last reviewed: 2026-05-11 by the Soarias team.

```