How Custom URL Schemes Work
A custom URL scheme turns a short prefix into an app launcher. You choose a scheme, such as shopapp, and register it with the operating system. From then on, when the system is asked to open a URL like shopapp://product/42?ref=email, it launches your app and passes it the URL. Your code parses the host, path and query, then navigates to the right screen.
The scheme is the part before ://. Everything after it is yours to define. Most teams mirror their web URL structure, so shopapp://product/42 matches https://shop.example.com/product/42, which keeps routing logic consistent between web and app.
Registering a Scheme
On iOS, register the scheme in the app's Info.plist under CFBundleURLTypes, with the scheme in CFBundleURLSchemes and a unique identifier. Xcode exposes this as URL Types on the target's Info tab. Incoming URLs arrive in scene(_:openURLContexts:) for scene-based apps, application(_:open:options:) for older app delegates, or onOpenURL in SwiftUI.
If your app needs to check whether another app is installed by testing its scheme with canOpenURL, list that scheme under LSApplicationQueriesSchemes. iOS limits how many schemes an app can query, to stop apps from scanning for installed apps.
On Android, add an intent filter to the target activity with android.intent.action.VIEW, the DEFAULT and BROWSABLE categories, and a data element with android:scheme, plus android:host and path attributes if you want to narrow it. Read the URI from the intent in onCreate and onNewIntent.
Security Considerations
Custom schemes have no ownership model. Any app can register shopapp://. On Android, two apps claiming the same scheme produce a chooser dialog. On iOS, which app opens is not defined. A malicious app can register a popular scheme and receive URLs meant for someone else.
That has practical consequences. Never send secrets through a custom scheme: no session tokens, no password reset tokens, no unprotected OAuth authorisation codes. OAuth flows that redirect to a custom scheme should use PKCE so an intercepted code is useless. Treat every incoming URL as untrusted input: validate the path, ignore unexpected parameters, and do not perform sensitive actions, such as payments or account changes, directly from a URL without confirmation.
Choose a distinctive scheme, such as one based on your reverse domain name, to reduce accidental collisions.
Custom Schemes vs Universal Links and App Links
Universal Links on iOS and App Links on Android solve the problems schemes have. They use HTTPS URLs on your own domain, verified through the apple-app-site-association and assetlinks.json files, so only your app can claim them. When the app is not installed, the URL opens your website instead of failing. And because they are normal web links, they work in emails, social posts and QR codes without looking strange.
Schemes still have a place. They are useful for app-to-app calls between apps you control, for OAuth redirects where a provider requires them, for widgets and shortcuts, and as the internal destination a deep linking SDK resolves to after a Universal Link or deferred deep link arrives. They are a poor choice for anything a user taps in a campaign.
Custom Schemes in Campaign Links
When a deep linking provider sends users into your app, the link a user taps is usually an HTTPS link. The provider then hands the app a destination, which your app may express internally as a path or a scheme URL. Linkrunner links use Universal Links and App Links on branded domains, with Linkrunner hosting the verification files, and deliver deferred deep links to new users after install. See deep linking for how links and in-app destinations are configured.
Testing Your Scheme
On iOS, type the scheme URL into Safari's address bar on a device or simulator, or use xcrun simctl openurl booted shopapp://product/42. On Android, use adb shell am start -a android.intent.action.VIEW -d "shopapp://product/42". Test cold starts and warm starts, malformed URLs, and URLs for screens that require login.