Add App Intents To Open SwiftUI Screens From Spotlight

App Intents are easy to think of as “that Shortcuts thing”, but they are becoming a much bigger entry point into apps. They can power Shortcuts, Spotlight actions, Siri, controls, widgets, and other system experiences that need to understand what your app can do.

A great first intent is not a big automation workflow. It is a simple foreground action that opens your app to a real screen.

Let’s build an intent that opens a notes screen.

The first version below uses the iOS 26, macOS 26, watchOS 26, tvOS 26, and visionOS 26 supportedModes API. If your app still deploys to older OS versions, use the compatibility note after the first snippet.

The navigator

The intent has to talk to app code somehow. In a real app, this might be your router, navigation model, app coordinator, or deep link handler. Here is a tiny placeholder:

@MainActor
final class NoteNavigator {
    static let shared = NoteNavigator()

    func openNotes() {
        // Route your SwiftUI app to the notes screen.
    }
}

The @MainActor annotation is deliberate. Opening UI is main-actor work, and App Intents can run from outside the normal button-tap path in your app.

The foreground intent

Now define the intent:

import AppIntents

struct OpenNotesIntent: AppIntent {
    static let title: LocalizedStringResource = "Open Notes"
    static let supportedModes: IntentModes = .foreground

    @MainActor
    func perform() async throws -> some IntentResult {
        NoteNavigator.shared.openNotes()
        return .result()
    }
}

There are three pieces worth calling out.

First, AppIntent needs a localized title. This is the human-readable name system surfaces can show.

Second, on the newest SDKs, supportedModes is the modern way to say how the intent runs. For this simple navigation intent, use .foreground. Older examples often used openAppWhenRun; that property is deprecated in the iOS 26 SDK in favor of supportedModes.

Third, perform() is async and returns an IntentResult. Even when your first version only routes inside the app, keeping the async shape makes it easy to add a fetch, validation step, or navigation parameter later.

If your deployment target is older than OS version 26, keep the new API behind availability and provide the older foregrounding hook:

@available(iOS 26.0, macOS 26.0, watchOS 26.0, tvOS 26.0, visionOS 26.0, *)
extension OpenNotesIntent {
    static var supportedModes: IntentModes { .foreground }
}

@available(*, deprecated)
extension OpenNotesIntent {
    static var openAppWhenRun: Bool { true }
}

Add an app shortcut

An intent can exist without an AppShortcut, but app shortcuts make common actions easier to discover and invoke.

struct NotesAppShortcuts: AppShortcutsProvider {
    static var appShortcuts: [AppShortcut] {
        AppShortcut(
            intent: OpenNotesIntent(),
            phrases: [
                "Open notes in \(.applicationName)"
            ],
            shortTitle: "Open Notes",
            systemImageName: "note.text"
        )
    }
}

The \(.applicationName) interpolation includes the required app-name placeholder, matching the shape Apple uses in its examples. Keep the phrase short and literal. People should not need to memorize your internal feature naming just to open a screen.

Wire it into SwiftUI navigation

The exact navigation code depends on your app. The important part is that the intent should call the same navigation path as a user action. That keeps the behavior testable and avoids a second routing system that only App Intents use.

One common shape is:

enum AppRoute: Hashable {
    case notes
}

Then your app’s router can append .notes to a NavigationStack path when NoteNavigator.shared.openNotes() is called.

Try to keep the intent thin:

  • Parse or validate parameters.
  • Call app code on the right actor.
  • Return a result.

Do not put view construction, database queries, or navigation stack surgery directly inside the intent if your app already has a place for that behavior.

Where to go next

Once the foreground version works, the next step is adding parameters. For example, OpenNoteIntent could take a note entity and open a specific note instead of the notes list. That is where AppEntity, display representations, and Spotlight indexing start to get interesting.

For parameterized Spotlight actions, make sure parameterSummary includes required parameters that do not have defaults. For opening indexed entities directly from Spotlight, look at OpenIntent, TargetContentProvidingIntent, and the current scene-based App Intent routing APIs, such as UISceneAppIntent or Apple’s latest Spotlight sample flow, instead of forcing everything through one generic foreground intent.

But the simple version is valuable on its own. It gives the system a typed action, gives users another way into the app, and gives you a clean base for richer intents later.

Further reading: