Skip to content

Documentation

iOS universal links setup

What a universal link is, the association file iOS fetches, the entitlement your app needs, and how to handle the incoming URL.

A universal link is an ordinary https:// address that opens your app when it is installed, and a web page when it is not. There is no custom scheme, no myapp://, and nothing for the person tapping it to know about.

It is entirely Apple’s mechanism. This page describes it because you need it whether or not you use us, and because you should be able to see how much of the work is actually ours.

The three pieces

  1. A file at a fixed path on your link host, saying which apps may open links there.
  2. An entitlement in your app, saying which hosts it wants to handle.
  3. Code in your app to read the incoming URL and route on it.

iOS checks that the first two agree. Neither side can claim the other unilaterally, which is what stops any app declaring itself the handler for somebody else’s domain.

The association file

When your app is installed, iOS fetches this over HTTPS from every host in your entitlements:

https://71k8c.qbrt.app/.well-known/apple-app-site-associationjson
{
  "applinks": {
    "details": [
      {
        "appIDs": ["A1B2C3D4E5.com.northwind.retail"],
        "components": [
          {
            "/": "/*",
            "comment": "Every path on this host opens the app"
          }
        ]
      }
    ]
  }
}

The value in appIDs is your Apple Team ID and your bundle identifier joined with a full stop. Those are the two things you give us, and they are all the file is made of.

Apple is strict about how it is served:

  • Over HTTPS, with a valid certificate. No plain HTTP, ever.
  • No redirects. A 301 to the same file elsewhere fails.
  • content-type: application/json.
  • No file extension. It is apple-app-site-association, not apple-app-site-association.json.

Not built yet. We serve this file for your link host as part of the redirect service, which is not built yet. You can enter the configuration today and see exactly what it will produce.

The entitlement

In Xcode, under Signing & Capabilities, add Associated Domains and one entry per host:

Northwind.entitlementsswift
applinks:71k8c.qbrt.app

The applinks: prefix is required. Add the host only — no scheme, no path.

Handling the link

In SwiftUI, one modifier:

NorthwindApp.swiftswift
import SwiftUI

@main
struct NorthwindApp: App {
    @StateObject private var router = Router()

    var body: some Scene {
        WindowGroup {
            RootView()
                .environmentObject(router)
                .onOpenURL { url in
                    // e.g. https://71k8c.qbrt.app/spring-sale
                    router.open(path: url.path)
                }
        }
    }
}

On UIKit, it arrives as a user activity:

AppDelegate.swiftswift
func application(
    _ application: UIApplication,
    continue userActivity: NSUserActivity,
    restorationHandler: @escaping ([UIUserActivityRestoring]?) -> Void
) -> Bool {
    guard userActivity.activityType == NSUserActivityTypeBrowsingWeb,
          let url = userActivity.webpageURL else {
        return false
    }

    router.open(path: url.path)
    return true
}

Things that cost people an afternoon

  • iOS caches the association file and will not re-fetch it because you rebuilt. Delete the app and reinstall to pick up a change.
  • Typing the link into Safari does not test anything. Safari treats an address you typed as a web request. Tap the link from Notes or Messages instead.
  • Following a universal link back to the same host stops working. Once you have tapped “open in Safari” on the banner, iOS remembers your preference for that host — which looks exactly like a broken link.
  • The Team ID is not the App ID prefix for older apps. Check Membership details in the developer portal rather than guessing.
  • The web page still has to be good. Everyone without the app gets it. That is why a web fallback address is mandatory here — a link cannot be saved without one.

What about people without the app?

They land on the web fallback. Taking somebody to the right screen after they install — deferred deep linking — needs something to remember the intent across the install, which is what an SDK is for.

Not built yet. Deferred deep linking, install attribution and the SDK they need are not built. See how it works for where they sit in the order of things.

Ask the documentation

It answers from these pages only, and links what it used. If the answer is not here it says so rather than guessing — then email [email protected].

← All documentation