Skip to main content
Tutorials

App Clip Tutorial: Build and Test an App Clip From Windows

Last updated

This App Clip tutorial starts from one fact: an App Clip is a small part of your full app that people can use right away by scanning a code, tapping an NFC tag or opening a link, without downloading anything from the App Store first. In this tutorial you add a scan-to-order App Clip to a SwiftUI project called MyApp. You create the target with kxbuild project add-target --type app-clip, handle the invocation URL, set up the associated domain and website file, and test on your iPhone with a local experience. Every step works on Windows.

The project in this tutorial already builds with KXBuild and installs on an iPhone. If you’re not there yet, start with Build an iOS app on Windows with SwiftUI and run it on your iPhone. Apple’s rules below follow the official documentation as of October 2026.

What is an App Clip good for?

An App Clip lives in the same project and the same .ipa as the full app, and goes through App Review and release together with it. People launch it in the ways listed below. The system first shows an App Clip card, and the App Clip opens within seconds after a tap on its button.

  • Scanning an App Clip Code or a QR code, or tapping an NFC tag
  • Tapping a Smart App Banner or an App Clip card on a web page in Safari
  • Tapping a link someone sent in Messages
  • Place links in Maps, Siri Suggestions and Spotlight search

A few rules differ from regular apps. Check that they fit your use case before you start.

Rule Details
Needs a full app An App Clip can’t be released on its own, and the full app must offer at least everything the App Clip does
Only one An app can have only one App Clip target. Use different invocation URLs for different scenarios
Not on the Home Screen People don’t install App Clips, so they don’t appear on the Home Screen, and the system removes them after a period of no use
After the full app is installed The full app replaces the App Clip, and from then on the same links and codes open the full app
No background work No background networking or background modes, and it can’t keep Bluetooth connections when not in use
Extensions It can’t include app extensions, except one widget extension for Live Activities
Privacy It can’t request tracking authorization or track location continuously, and it can share data only with its own full app

Some frameworks also compile in an App Clip but do nothing at runtime, such as HealthKit, Contacts, Calendar, Photos, CoreMotion, CallKit and background tasks. Leave features that need them to the full app.

Size limits

An App Clip has to open almost instantly, so its size is capped. The limit applies to the uncompressed App Clip binary and depends on the minimum iOS version it supports.

iOS version Uncompressed size limit
iOS 15 and earlier 10 MB
iOS 16 15 MB
iOS 17 and later 100 MB, with conditions

The 100 MB limit applies only when all of these are true. The App Clip supports only digital invocations (websites, Spotlight and so on), not physical ones such as App Clip Codes, QR codes or NFC. People mostly use it where the network is reliable. It doesn’t support iOS 16 or earlier. With a demo link generated in App Store Connect, you can also use the 100 MB limit and support physical invocations, which works well for game demos.

App Clips added by KXBuild support iOS 16 and later by default, so keep them under 15 MB. After you build, rename the .ipa to .zip and extract it. The size of the Payload\MyApp.app\AppClips\MyAppClip.app folder is the size before thinning, so if it’s under the limit you’re safe. Host large assets such as images and audio on a server and download them on demand where you can.

How do you add an App Clip target?

Use either approach.

  • VS Code extension. In the KXBuild view, right-click the MyApp target, choose KXBuild: Add Target…, pick App Clip as the type and keep the default name MyAppClip. Enter an App Group if it shares data with the app, or leave it empty for now.
  • Command line. Run the command below in the project folder.
1kxbuild project add-target -P .\MyApp.xcodeproj --type app-clip --name MyAppClip

The App Clip’s Bundle ID is the app’s Bundle ID plus .Clip, here com.example.myapp.Clip. This follows Apple’s rule that an App Clip’s identifier must start with the full app’s identifier followed by one more component. For every type and option, see project add-target in the kxbuild reference and Add a target.

The command output reminds you to do two things. Register an App ID for com.example.myapp.Clip, and enable the App Clips capability on both the com.example.myapp.Clip and com.example.myapp App IDs. The section “Prepare App IDs and provisioning profiles” below walks through both.

What files does the App Clip target generate?

A new MyAppClip folder appears in the project folder, and it builds as is.

File Purpose
MyAppClipApp.swift The App Clip’s entry point, with code that already receives the invocation URL
ContentView.swift The App Clip’s UI
Info.plist The NSAppClip dictionary, with both switches off by default
MyAppClip.entitlements Parent Application Identifiers, pointing to the full app
Assets.xcassets The App Clip’s own icon and accent color

On the app side you get MyApp/MyApp.entitlements. If the project already had an entitlements file, the key is added to that file instead. The two entitlements are shown below. One says who the parent app is, the other says who the App Clip is, and the system accepts the pair only when both match.

 1<!-- MyAppClip/MyAppClip.entitlements -->
 2<key>com.apple.developer.parent-application-identifiers</key>
 3<array>
 4	<string>$(AppIdentifierPrefix)com.example.myapp</string>
 5</array>
 6
 7<!-- MyApp/MyApp.entitlements -->
 8<key>com.apple.developer.associated-appclip-app-identifiers</key>
 9<array>
10	<string>$(AppIdentifierPrefix)com.example.myapp.Clip</string>
11</array>

Parent Application Identifiers can have only one entry, the full app’s identifier, and goes only on the App Clip. $(AppIdentifierPrefix) becomes your Team ID plus a dot at signing time, so leave it as is. If you change the app’s Bundle ID later, change both places together.

The App Clip is already set to build with the app and to be embedded in the app’s AppClips folder, so you don’t need to change any project settings.

Turn on the two NSAppClip switches in Info.plist as needed.

Key When on
NSAppClipRequestEphemeralUserNotification The App Clip can send notifications for 8 hours after each launch without asking for permission. The card says so, and people can turn it off
NSAppClipRequestLocationConfirmation The App Clip can use a system-provided check to confirm the user is near a specific place, useful for in-store scenarios

How does an App Clip handle the invocation URL?

Each time an App Clip is invoked, the system hands it the invocation URL in an NSUserActivity of type NSUserActivityTypeBrowsingWeb. The App Clip should go straight to the matching content based on the URL. For example, scanning the QR code on a table opens that store’s menu, without asking the user to choose again.

Replace MyAppClip/MyAppClipApp.swift with the code below. It supports two URL forms, your own site’s https://clip.example.com/store/42 and Apple’s default link https://appclip.apple.com/id?=com.example.myapp.Clip&store=42.

 1import SwiftUI
 2
 3@main
 4struct MyAppClipApp: App {
 5    // Returning from a notification or the app switcher brings no URL, so use the last saved store
 6    @AppStorage("lastStoreID") private var storeID = ""
 7
 8    var body: some Scene {
 9        WindowGroup {
10            ContentView(storeID: storeID)
11                .onContinueUserActivity(NSUserActivityTypeBrowsingWeb) { activity in
12                    if let id = ClipLink.storeID(from: activity.webpageURL) {
13                        storeID = id
14                    }
15                }
16        }
17    }
18}
19
20enum ClipLink {
21    /// Supports two invocation URL forms:
22    /// https://clip.example.com/store/42
23    /// https://appclip.apple.com/id?=com.example.myapp.Clip&store=42
24    static func storeID(from url: URL?) -> String? {
25        guard let url,
26              let components = URLComponents(url: url, resolvingAgainstBaseURL: true) else { return nil }
27        if let value = components.queryItems?.first(where: { $0.name == "store" })?.value, !value.isEmpty {
28            return value
29        }
30        let parts = url.pathComponents
31        if let index = parts.firstIndex(of: "store"), index + 1 < parts.count {
32            return parts[index + 1]
33        }
34        return nil
35    }
36}

Then replace MyAppClip/ContentView.swift with the code below. After the user orders, an overlay at the bottom recommends the full app.

 1import SwiftUI
 2import StoreKit
 3
 4struct ContentView: View {
 5    let storeID: String
 6    @State private var ordered = false
 7    @State private var showFullApp = false
 8
 9    var body: some View {
10        VStack(spacing: 16) {
11            Image(systemName: "cup.and.saucer.fill")
12                .font(.system(size: 56))
13                .foregroundStyle(.tint)
14            if storeID.isEmpty {
15                Text("Scan the QR code on your table to order")
16                    .font(.title3)
17            } else {
18                Text("Store \(storeID)")
19                    .font(.title.bold())
20                Button(ordered ? "Ordered" : "Order a latte") {
21                    ordered = true
22                    showFullApp = true
23                }
24                .buttonStyle(.borderedProminent)
25                .disabled(ordered)
26            }
27        }
28        .padding()
29        // Recommend the full app at the bottom once the task is done
30        .appStoreOverlay(isPresented: $showFullApp) {
31            SKOverlay.AppClipConfiguration(position: .bottom)
32        }
33    }
34}

Plan for every way the App Clip can be opened.

How the user opens it The URL it receives
Scanning a code, NFC, a link, a website banner The URL in the code or link, or the web page’s URL
Maps, Siri Suggestions The URL you registered for the experience in App Store Connect
Returning to a used App Clip from the App Library or Spotlight The last invocation URL
Returning from a notification or the app switcher No URL, so restore the last state yourself

That’s why the code above remembers the last store with @AppStorage. Don’t let the overlay that recommends the full app block the main flow. Apple doesn’t allow requiring people to install the full app to finish a task.

Handle the same URLs in the full app

Once the full app is installed, the same QR code opens the full app. Replace MyApp/MyAppApp.swift with the code below, which ends with the same ClipLink as the App Clip.

 1import SwiftUI
 2
 3@main
 4struct MyAppApp: App {
 5    @State private var storeID: String?
 6
 7    var body: some Scene {
 8        WindowGroup {
 9            ContentView()
10                // Once the full app is installed, the same links open it, so handle the same URLs here
11                .onContinueUserActivity(NSUserActivityTypeBrowsingWeb) { activity in
12                    storeID = ClipLink.storeID(from: activity.webpageURL)
13                    print("Opening store \(storeID ?? "-")")
14                }
15        }
16    }
17}
18
19enum ClipLink {
20    static func storeID(from url: URL?) -> String? {
21        guard let url,
22              let components = URLComponents(url: url, resolvingAgainstBaseURL: true) else { return nil }
23        if let value = components.queryItems?.first(where: { $0.name == "store" })?.value, !value.isEmpty {
24            return value
25        }
26        let parts = url.pathComponents
27        if let index = parts.firstIndex(of: "store"), index + 1 < parts.count {
28            return parts[index + 1]
29        }
30        return nil
31    }
32}

To carry data from the App Clip over to the full app, give both targets the same App Group and read and write through UserDefaults(suiteName:) or the shared container, the same way the widget tutorial shares data. Keep sign-in state in the keychain or use Sign in with Apple, so people don’t have to sign in again after they switch to the full app.

How do you prepare App IDs and provisioning profiles?

An App Clip is signed separately and has its own App ID and provisioning profile. Sign in to developer.apple.com and open Certificates, Identifiers & Profiles. You need the Account Holder or Admin role.

  1. Register the App Clip’s App ID. On the Identifiers page, click “+”, choose App IDs and select App Clip as the type. Pick the parent app’s App ID com.example.myapp from the menu and enter Clip as the Product Name. It’s appended to the parent App ID, giving com.example.myapp.Clip.
  2. Enable capabilities. As the add-target output said, enable the App Clips capabilities on both the App Clip and full app App IDs. If you’ll use an associated domain in the next section, also check Associated Domains.
  3. Generate provisioning profiles. On the Profiles page, create a Development profile for com.example.myapp.Clip and select your certificate and test devices. Since the full app’s App ID changed, open its existing profile, click Edit, then Save without changes, and download it again.
  4. Put them in the keychain folder. Put both profiles in KXBuild’s keychain folder and delete the old files. At build time KXBuild matches a profile to the app and to the App Clip by Bundle ID.

For signing rules for sub-targets such as extensions and App Clips, see Sign iOS app extensions: certificates and profiles for share and notification service extensions. For the full steps on profiles and the certificate folder, see Certificates and signing.

Associated domain and website file

To invoke the App Clip with your own URL, the App Clip and the website have to confirm each other, the same way Universal Links work. You don’t always need it, so check the table first.

Scenario Associate a website?
Testing QR codes and NFC with a local experience during development No
Using only Apple’s default appclip.apple.com link No
Showing a Smart App Banner and App Clip card on your website Yes
Advanced experiences (App Clip Codes, Maps places, a different card per store) Yes
Supporting every invocation method on iOS 16.3 and earlier Yes
Setting up App Clip experiences for testers in TestFlight Yes

Step 1: add the appclips domain to both targets

Add the key below to both MyAppClip/MyAppClip.entitlements and MyApp/MyApp.entitlements, keeping what’s already there. You can also double-click an .entitlements file and edit it in the Property List Editor.

1<key>com.apple.developer.associated-domains</key>
2<array>
3	<string>appclips:clip.example.com</string>
4</array>

The format is appclips: followed by the full domain, meaning only the subdomain and top-level domain, without https://, paths, query parameters, a trailing slash or a * wildcard. If the full app already has Universal Links, add it next to the applinks: entry in the same array. Both targets’ entitlements files are already set in the build settings, so you don’t need to set CODE_SIGN_ENTITLEMENTS.

Check Associated Domains on both App IDs and regenerate the provisioning profiles, or signing fails because the profile doesn’t include the capability.

Step 2: host apple-app-site-association on your website

Put a JSON file with no extension at https://clip.example.com/.well-known/apple-app-site-association and add an appclips section. The apps entry is the Team ID, a dot and the App Clip’s Bundle ID.

 1{
 2  "appclips": {
 3    "apps": ["ABCDE12345.com.example.myapp.Clip"]
 4  },
 5  "applinks": {
 6    "details": [
 7      {
 8        "appIDs": ["ABCDE12345.com.example.myapp"],
 9        "components": [{ "/": "/store/*" }]
10      }
11    ]
12  }
13}

The applinks section is for the full app’s Universal Links, so the same links can open the full app once it’s installed. Serve the file directly over HTTPS without redirects, and let the AASA-Bot and CFNetwork user agents through. Every domain that invokes the App Clip needs its own copy of the file.

For the file format, hosting requirements and Apple’s CDN caching, see Set up Universal Links and URL schemes and test them on iPhone. After you upload a build and create an experience, click View Status in the App Clip section of the build details page in App Store Connect to see the validation status of each domain.

Test on your iPhone with a local experience

Local Experiences is a feature in the iPhone’s Developer settings. It lets you invoke the App Clip with a real QR code or NFC tag and see the full card and launch flow, without releasing anything or setting up a website.

Step 1: install the App Clip on your phone

  1. Delete the full MyApp from your phone first. While the full app is installed, invocations open it instead of the App Clip.
  2. Connect the iPhone with a USB cable and make sure Developer Mode is on. See Device setup.
  3. Select your iPhone in the Status Bar and press F5. The project has two app targets, MyApp and MyAppClip, so you’re asked to choose one. Pick MyAppClip (com.example.myapp.Clip).

To always run the App Clip with F5, set it with the bundle field in .vscode/launch.json. The fields are described in Run and debug.

1{
2  "version": "0.2.0",
3  "configurations": [
4    { "type": "kxbuild", "request": "launch", "name": "Run App Clip", "build": true, "bundle": "com.example.myapp.Clip" }
5  ]
6}

Once the App Clip launches, breakpoints, variables and KXBuild Console output work just as in a regular app. Launched directly, it has no invocation URL and shows “Scan the QR code on your table to order”, which is expected.

Step 2: register a local experience

On the iPhone, open Settings > Developer, tap Local Experiences in the App Clips Testing section, then tap Register Local Experience and fill in the fields below.

Field What to enter
URL Prefix The invocation URL or a prefix of it, such as https://clip.example.com/store
Bundle ID The App Clip’s Bundle ID, com.example.myapp.Clip
Title The card title, up to 30 characters, such as “Corner Café”
Subtitle The card subtitle, up to 56 characters, such as “Scan, order, skip the line”
Action The card’s button text, such as Open
Image The header image at the top of the card

A URL prefix matches every URL that starts with it, so https://clip.example.com/store covers /store/42 and /store/43. Local experiences take priority over experiences configured in App Store Connect.

If you don’t see Developer in Settings, make sure Developer Mode is on, then connect the phone to your computer and install the app once with F5.

Step 3: scan to invoke the App Clip

  1. Use any QR code generator to turn https://clip.example.com/store/42 into a QR code and show it on your computer screen, or write it to an NFC tag.
  2. Scan it with the iPhone’s Camera or the Code Scanner in Control Center. You can add Code Scanner and NFC Tag Reader to Control Center in Settings > Control Center.
  3. An App Clip card slides up from the bottom with the title, subtitle and header image you entered. Tap the button to open the App Clip, which shows “Store 42”.

Scan again with https://clip.example.com/store/43 and check that it goes straight to the other store. Switch back to the App Clip from the app switcher and check that it’s still on the last store.

Keep these points in mind while testing.

  • Local experiences can only invoke App Clips signed for Development, Ad Hoc or TestFlight, not the App Store version.
  • A local experience can’t test the case where the full app is installed and opens instead. To check that, install the full app and open the same URL as a Universal Link, as described in Verify on your iPhone in the Universal Links tutorial.
  • Delete the local experience before you test the real experience from App Store Connect, or the local one takes priority.
  • If an advanced experience you created in App Store Connect never invokes the App Clip, tap Clear Experience Cache in Settings > Developer.

What do you configure in App Store Connect before release?

The App Clip is packaged and uploaded together with the full app. Run KXBuild: Build for App Store (.ipa) from the Command Palette or the project’s context menu, or run the command below.

1kxbuild build .\MyApp.xcodeproj -c Release --export app-store

Before release, also generate an App Store provisioning profile for com.example.myapp.Clip and put it in keychain. Keep the App Clip’s version and build numbers the same as the app’s, and change both targets together in the Project Settings Editor. The resulting build\MyApp.ipa already includes the App Clip. For uploading, see Publish to the App Store on Windows, from .ipa upload to review.

After the upload finishes processing, configure experiences in App Store Connect.

Experience Where to configure it What it includes
Default experience (required) The App Clip section of the app version page Card header image, subtitle and button text. The system generates the default link https://appclip.apple.com/id?=<bundle id>&key=value, with no server changes
Demo link Generated automatically when you configure the default experience For demos. It can use the 100 MB limit but can’t carry URL parameters
Advanced experiences (optional) Edit Advanced Experiences on the app version page One card per store or place, can link to a Maps place and generate App Clip Codes. Requires an associated website
TestFlight test experiences The App Clip section of the build details page Up to three. Testers open them from TestFlight. Requires an associated website

The default link and demo link work only after the app and App Clip pass App Review and are on the App Store, so use local experiences during development. After release, tap Diagnostics in the App Clips Testing section of Settings > Developer and enter a URL. The system checks whether advanced experiences, the website association and the Smart App Banner are all set up correctly.

For TestFlight uploads and managing testers, see TestFlight beta testing: upload a build and add internal and external testers.

How do you troubleshoot an App Clip that won’t open?

Symptom Cause Fix
The build can’t find a provisioning profile for com.example.myapp.Clip The App Clip’s App ID isn’t registered, or its profile isn’t in keychain Register the App ID and generate a profile as described above
The developer portal says the App ID doesn’t include the parent App ID The App Clip’s App ID has the wrong parent, or the parent identifier in the entitlements doesn’t match the app’s Bundle ID Register the App ID again as an App Clip type and check parent-application-identifiers
Signing says the profile doesn’t include Associated Domains The App ID doesn’t have Associated Domains checked, or the profile was downloaded before the capability changed Check it on both App IDs, then regenerate and replace the profiles
Scanning opens the full app The full app is installed on the phone Delete the full app before testing the App Clip
Scanning doesn’t show a card No local experience is registered, or the URL prefix or Bundle ID is wrong Check it in Settings > Developer > Local Experiences
The card shows, but the App Clip opens blank or on the default screen The code doesn’t handle this URL Print the URL in onContinueUserActivity and compare it with your parsing logic
Domain validation fails in App Store Connect The AASA file path or format is wrong, or the server blocks Apple’s requests Check the .well-known path, the appclips section and the allowed user agents
Upload says the App Clip is too large It exceeds the size limit for its iOS version Move large assets to on-demand downloads, or use the 100 MB limit if you meet its conditions

For other App Clip signing and installation errors, see 16 common iOS code signing and .ipa install errors, and how to fix them.

FAQ

What is the App Clip size limit?

It’s measured on the uncompressed binary. The limit is 10 MB for iOS 15 and earlier, 15 MB for iOS 16, and up to 100 MB for iOS 17 and later, as long as the App Clip uses only digital invocations and doesn’t support iOS 16, or uses a demo link generated in App Store Connect.

How should I name an App Clip’s Bundle ID?

It must start with the full app’s Bundle ID followed by one more component, usually .Clip, for example com.example.myapp.Clip. When you register it in the developer portal, choose the App Clip type, pick the parent App ID and then enter the suffix.

Does an App Clip need an associated domain?

Not always. You don’t need one to test QR codes and NFC with a local experience during development, or if you only use Apple’s default link. You need the appclips: domain and the website file to show App Clip cards on your website, set up advanced experiences, support iOS 16.3 and earlier, or test experiences in TestFlight.

Where do I set up a local experience on iPhone?

Open Settings > Developer, tap Local Experiences in the App Clips Testing section, then tap Register Local Experience and enter the URL prefix, the App Clip’s Bundle ID, a title, a subtitle and the button text. If you don’t see Developer, turn on Developer Mode and install an app once from your computer.

Can the App Clip still open after the full app is installed?

No. The full app replaces the App Clip, and from then on the same QR codes and links open the full app, so the full app must handle every invocation URL too. Delete the full app from your phone before you test the App Clip.