A QR code that opens your app is one of the best uses of a code, and one of the most often broken. The camera only understands web links, the two phone platforms handle them differently, and a well-known shortcut for all of this closed in 2025. This guide walks through what to encode, how to make it open the app, and what to do when the app is not installed.
What a deep link is
A deep link is a link that opens a specific screen inside an app rather than the app’s home screen or a web page. Scan a code on a product and land on that product in the app. Scan a code on a ticket and open that ticket. The alternative, sending everyone to the App Store listing, loses the context they scanned for. The app store type page covers the plain store link for when that is all you need.
Do this
A code on a shelf label that opens the product's page in the app if installed, or the same product on the website if not.
Not this
A code that opens the App Store, leaving the shopper to find the product again after installing.
Step 1: encode an https link, never a custom scheme
Apps used to register custom schemes like myapp://product/42. They still work when tapped inside an app, but a phone camera is not an app that opens arbitrary schemes. iOS Camera and most Android scanners only act on http and https links. Encode a real web address on your own domain, such as https://example.org/p/42. That link must also be a working web page, because it is the fallback for everyone else.
Do this
https://example.org/p/42, which is a real page and also a claimed deep link.
Not this
myapp://product/42, which the camera shows as text and cannot open.
Worked example: a code for a recipe. Encode https://example.org/r/lemon-tart. On a phone with the app, it opens the recipe screen in the app. On any other device, it shows the recipe web page.
Step 2: claim the domain on iOS with Universal Links
Universal Links are Apple’s way of letting an app own https links on a domain. Two pieces are needed. Your app declares the domain under Associated Domains in its entitlements. Your web server publishes a small JSON file called apple-app-site-association at https://example.org/.well-known/apple-app-site-association, listing the app and which paths it handles. When iOS sees a link on that domain, it checks the file and, if the app is installed, hands the link to the app. The Camera app then offers to open the link in your app instead of Safari.
Do this
The association file served over https with the correct content type, listing /p/* and /r/* as app paths.
Not this
The file only on the staging server, so production links open Safari every time.
Step 3: claim the domain on Android with App Links
App Links are the Android equivalent. The app declares intent filters for the domain with autoVerify enabled, and the server publishes https://example.org/.well-known/assetlinks.json listing the app’s package name and signing certificate fingerprint. Android verifies the file at install time. If it checks out, links on that domain open the app without asking. If not, the user gets a chooser or the browser.
Do this
assetlinks.json listing the release signing certificate, verified on a fresh install.
Not this
assetlinks.json listing only the debug certificate, so production installs never verify and links open Chrome.
Step 4: make the web page a real fallback
Most people who scan will not have the app. A tablet, a laptop camera, a friend’s phone, a new customer. The web page at the link must show them the same content and, if you like, a way to get the app. Do not make the fallback a blank page with a store button. That is the dead code problem in a new coat.
Do this
The recipe on the web, with a small Get the app banner at the top.
Not this
A page that says Download the app to continue and nothing else.
Step 5: decide whether you need deferred deep linking
Deferred deep linking handles the person who scans, does not have the app, installs it, and then expects to land on the thing they scanned. The store visit breaks the chain, so something has to remember the link for them. That is normally a deep-linking service that records the click, matches the first app launch to it, and passes the original link into the app.
It is worth having when the code leads to a specific offer, ticket or referral where losing context costs the conversion. It is not worth the dependency for a code that opens a general product page.
Do this
Deferred deep linking on a referral code, so the new user's first launch shows the referrer's offer.
Not this
Deferred deep linking on a code that opens the app's home screen, adding a service for no benefit.
Firebase Dynamic Links closed in 2025
Firebase Dynamic Links was a free Google service that did all of this: one short link that opened the app if installed, the store if not, and remembered the deep link across install. Google shut it down on 25 August 2025, and links stopped resolving. Any QR code printed with a page.link address, or a custom Dynamic Links domain, now leads nowhere unless the owner set up their own redirect.
If you have codes from that era, check them. If you are starting now, the options in general terms are:
- Your own domain with Universal Links, App Links and a web fallback. No third party, no shutdown risk, but no deferred deep linking unless you build it.
- A deep-linking service from a vendor that offers hosted links, deferred deep linking and analytics. Convenient, but the link lives on their domain or a domain they manage, so read the terms and plan for the day they change.
- Your own domain in the code, redirecting to a service behind it. The printed link is yours, so a change of vendor is a server change, not a reprint.
| Feature | Own domain, Universal and App Links | Deep-linking service | Custom scheme |
|---|---|---|---|
| Opens from the camera app | ✓ | ✓ | ✕ |
| Opens the app when installed | ✓ | ✓ | Only from inside another app |
| Web fallback when not installed | ✓ | ✓ | ✕ |
| Deferred deep linking after install | Only if you build it | ✓ | ✕ |
| No third-party dependency | ✓ | ✕ | ✓ |
| Printed link survives a vendor change | ✓ | Only if you own the domain | Not applicable |
Common mistake
Printing a link on a vendor’s domain. When the vendor closes, is bought or changes its pricing, every printed code changes with it. Put your own domain in the code and redirect from there. A reprint is far more expensive than a DNS record.
Step 6: test on both platforms
Test four cases on each platform: app installed and not installed, and the link scanned from the camera and pasted into a browser. Check that iOS shows the Open in app banner, that Android opens the app without a chooser, and that a laptop lands on the web page. Then put the exported code through the decoder to confirm it holds exactly the link you meant, and follow the rest of the deployment checklist.
Do this
Tested installed and not installed on an iPhone and an Android phone, from the camera and a browser, before printing.
Not this
Tested once on the developer's phone with the debug build installed.
Try it in QR Studio
QR Studio’s dynamic codes put a short link on a stable domain in the printed code and let you change where it goes later, so a change of deep-linking vendor or a new app version never means reprinting.
Checklist
- Code holds an https link on your own domain, not a custom scheme
- The link is a working web page with the same content as the app screen
- apple-app-site-association served at /.well-known/ over https on production
- assetlinks.json served at /.well-known/ with the release certificate fingerprint
- Web fallback shows the content, not only a store button
- Deferred deep linking used only where losing context costs a conversion
- No printed links on a vendor’s domain, or a redirect from your domain in front of it
- Old Firebase Dynamic Links codes found and redirected or replaced
- Tested installed and not installed on iOS and Android, from camera and browser
- Exported code decoded to confirm the exact link
Try it yourself
Drop the exported code in the decoder. If it shows a custom scheme or a page.link address, fix the link before anything is printed.