Skip to content
Tolinku
Tolinku
Sign In Start Free
Deep Linking · · 5 min read

Deep Linking and App Clips: Instant Experiences

By Tolinku Staff
|
Tolinku deep linking fundamentals dashboard screenshot for deep linking blog posts

App Clips let users access a small part of your app without installing the full app. They launch from URLs, NFC tags, QR codes, or Maps, and they use the same Universal Links infrastructure that powers deep linking. This means App Clips and deep links are not separate features; they are the same routing system with different destinations.

This guide covers how to build App Clips that work with your deep link strategy. For the comparison between Universal Links and App Clips, see Universal Links vs App Clips. For deep linking on Android (Instant Apps), see Android Instant Apps and deep linking.

The Routing Logic

When a user taps a URL associated with your app:

URL tapped (e.g., https://yourapp.com/order/store-123)
  → iOS checks: is the full app installed?
    → Yes: open the full app via Universal Links
    → No: is there a registered App Clip for this URL?
      → Yes: download and launch the App Clip (~10MB)
      → No: open the URL in Safari

The URL is the same for both the full app and the App Clip. You do not need separate URLs. iOS handles the routing based on what is installed.

App Clip URL Configuration

Register your App Clip URLs in App Store Connect and your apple-app-site-association file:

{
  "appclips": {
    "apps": ["TEAM_ID.com.yourapp.Clip"]
  },
  "applinks": {
    "apps": [],
    "details": [
      {
        "appIDs": ["TEAM_ID.com.yourapp"],
        "components": [
          { "/": "/order/*" },
          { "/": "/menu/*" },
          { "/": "/products/*" }
        ]
      }
    ]
  }
}

App Clip Target Setup

An App Clip is a separate target in your Xcode project that shares code with the full app:

// AppClip/AppClipApp.swift
import SwiftUI

@main
struct OrderClip: App {
    var body: some Scene {
        WindowGroup {
            ClipContentView()
                .onContinueUserActivity(NSUserActivityTypeBrowsingWeb) { activity in
                    guard let url = activity.webpageURL else { return }
                    handleDeepLink(url)
                }
        }
    }

    func handleDeepLink(_ url: URL) {
        let pathComponents = url.pathComponents

        if pathComponents.contains("order"), let storeId = pathComponents.last {
            NavigationState.shared.navigate(to: .orderFromStore(storeId))
        } else if pathComponents.contains("menu"), let menuId = pathComponents.last {
            NavigationState.shared.navigate(to: .viewMenu(menuId))
        }
    }
}

Invocation URL Handling

The App Clip receives the invocation URL through NSUserActivity. Parse it the same way you parse Universal Links in the full app:

class SceneDelegate: UIResponder, UIWindowSceneDelegate {
    func scene(_ scene: UIScene, continue userActivity: NSUserActivity) {
        guard userActivity.activityType == NSUserActivityTypeBrowsingWeb,
              let url = userActivity.webpageURL else { return }

        // Extract context from the URL
        let context = DeepLinkParser.parse(url)

        switch context {
        case .storeOrder(let storeId):
            showOrderFlow(storeId: storeId)
        case .productView(let productId):
            showProduct(productId: productId)
        case .menuView(let menuId):
            showMenu(menuId: menuId)
        default:
            showDefaultExperience()
        }
    }
}

Shared Code Between App and Clip

Since both the full app and App Clip handle the same URLs, share the deep link parsing logic:

// Shared/DeepLinkParser.swift (included in both targets)
enum DeepLinkDestination {
    case storeOrder(String)
    case productView(String)
    case menuView(String)
    case unknown
}

struct DeepLinkParser {
    static func parse(_ url: URL) -> DeepLinkDestination {
        let path = url.pathComponents

        if path.contains("order"), let id = path.last, id != "order" {
            return .storeOrder(id)
        }
        if path.contains("products"), let id = path.last, id != "products" {
            return .productView(id)
        }
        if path.contains("menu"), let id = path.last, id != "menu" {
            return .menuView(id)
        }

        return .unknown
    }
}

App Clip Invocation Sources

NFC Tags

NFC tags at physical locations can trigger App Clips. Encode your deep link URL in the NFC tag:

NFC tag at restaurant table → https://yourapp.com/order/restaurant-abc/table-5
  → App Clip launches with table context pre-loaded
  → User sees menu and can order immediately

QR Codes

QR codes work the same way as any URL invocation. The QR code encodes a URL; iOS checks if an App Clip is registered for that URL:

QR code on parking meter → https://yourapp.com/parking/meter-1234
  → App Clip launches with meter pre-selected
  → User taps "Pay" and uses Apple Pay

App Clip Codes

App Clip Codes are Apple-designed visual codes that combine NFC and visual scanning. They are more reliable than QR codes for App Clip invocation because iOS recognizes them natively.

Maps and Siri Suggestions

If your app has a physical presence (restaurants, stores, services), register your locations in Apple Business Register. Users see your App Clip in Maps when viewing your location.

Transitioning from App Clip to Full App

Preserving User Data

When a user upgrades from the App Clip to the full app, preserve their data using a shared App Group:

// In both App Clip and full app
let sharedDefaults = UserDefaults(suiteName: "group.com.yourapp.shared")

// App Clip: save order history
func saveOrderToSharedStorage(_ order: Order) {
    var orders = sharedDefaults?.array(forKey: "orderHistory") as? [[String: Any]] ?? []
    orders.append(order.toDictionary())
    sharedDefaults?.set(orders, forKey: "orderHistory")
}

// Full app: restore order history from App Clip
func restoreOrderHistory() -> [Order] {
    guard let data = sharedDefaults?.array(forKey: "orderHistory") as? [[String: Any]] else {
        return []
    }
    return data.compactMap { Order(dictionary: $0) }
}

When the user installs the full app, the same URLs that triggered the App Clip now open the full app. No URL changes needed. This is the key advantage of using Universal Links as the foundation for both.

App Clip Limitations

App Clips have constraints that affect your deep link strategy:

Limitation Impact
15MB size limit (was 10MB, increased in iOS 16) Keep the App Clip focused on one task
No background processing Cannot pre-fetch deep link content in background
Limited API access No HealthKit, CallKit, or certain sensor APIs
8-hour active period App Clip is removed after inactivity (data preserved in App Group)
No push notifications without permission Cannot re-engage users without explicit opt-in

URL-Specific Experiences

Each App Clip invocation URL should lead to a focused experience:

/order/store-123        → ordering flow for store 123
/menu/restaurant-abc    → menu viewer for restaurant abc
/parking/meter-1234     → parking payment for meter 1234

Do not try to replicate the full app in the App Clip. Each URL pattern should map to one specific task.

Tolinku and App Clips

Tolinku manages the Universal Link routing that powers both your full app and App Clip. Configure routes in the Tolinku dashboard, and Tolinku serves the apple-app-site-association file with both your full app and App Clip bundle IDs. The same deep link URL works for Universal Links, App Clips, and web fallback.

For more on deep linking trends, see the future of mobile deep linking.

Get deep linking tips in your inbox

One email per week. No spam.

Ready to add deep linking to your app?

Set up Universal Links, App Links, deferred deep linking, and analytics in minutes. Free to start.