162

I want to create a web-page, a page that will redirect an iPhone to the App Store if the iPhone does not have the application installed, but if the iPhone has the app installed I want it to open the application.

I have already implemented a custom URL in the iPhone application, so I have a URL for the application that is something like:

myapp://

And if this URL is invalid, I want the page to redirect to the App Store. Is this possible at all?

If I don't have the application installed on the phone and write the myapp:// URL in Safari, all I get is an error message.

Even if there exists an ugly hack with JavaScript, I would really like to know.

Peter Mortensen
  • 28,342
  • 21
  • 95
  • 123
Joakim Engstrom
  • 5,923
  • 12
  • 46
  • 66
  • 1
    This keeps changing in every iOS version - iOS9 just broke everything again. I'd recommend using a service like https://branch.io to take care of this for you. I helped build parts of the Branch link service, and it currently handles over 6000 different redirection edge cases... crazy. – Alex Austin Nov 16 '15 at 05:02
  • 1
    In 2017, if your need is to link to your app from emails, you should rather take a look at my answer: https://stackoverflow.com/questions/13044805/how-to-check-if-an-app-is-installed-from-a-web-page-on-an-iphone/44671031#44671031 – Sebastien Lorber Jun 21 '17 at 08:44

10 Answers10

221

As far as I know you can not, from a browser, check if an app is installed or not.

But you can try redirecting the phone to the app, and if nothing happens redirect the phone to a specified page, like this:

setTimeout(function () { window.location = "https://itunes.apple.com/appdir"; }, 25);
window.location = "appname://";

If the second line of code gives a result then the first row is never executed.

Similar questions:

Peter Mortensen
  • 28,342
  • 21
  • 95
  • 123
missemisa
  • 2,350
  • 1
  • 11
  • 10
  • 22
    Currently in iOS 6.1.2 this seems to work only in webapp mode. If using the app directly from browser it opens the external app as expected, but the timeout also gets fired and browser is redirected. – Mika Tuupola May 17 '13 at 07:16
  • I needed a solution like this for iOS and Android. I found this answer integrated in a cross-platform solution here: https://gist.github.com/FokkeZB/6635236#file-all-in-one-php – Justin Oct 14 '14 at 00:43
  • 5
    If the app is not installed it throws an ugly 'page not found' alert. Anyway we can suppress that – Akshat Agarwal Oct 08 '15 at 15:32
  • Maybe try in an iframe first? – 1.21 gigawatts Nov 29 '15 at 01:56
  • 3
    @AkshatAgarwal, can you please elaborate on how to code to suppress 'page not found alert' – Harpreet Mar 31 '16 at 13:52
  • @missemisa i dont think this solution really works in ios9..Now it shows popup to open app..And that popup holds only for seconds – Anish Parajuli 웃 Jun 30 '16 at 10:14
  • Anish pointed out it doesn't work now due to the popup. My solution was to give it 10 seconds, then forward. I also check if it's an iOS device, otherwise it's 25ms. This gives the end-user 10 seconds to decide, otherwise they get the browser. Not ideal, but it works. – Caleb Pitman Nov 03 '16 at 17:24
  • how does `branch.io` does this without timeout? – Vikas Bansal Sep 28 '17 at 11:35
  • 2
    There are new ways of implementing this now in both Android and iOS. Take a look at [Android App Links](https://developer.android.com/training/app-links/index.html#add-app-links) and [iOS Universal Links](https://developer.apple.com/library/content/documentation/General/Conceptual/AppSearch/UniversalLinks.html) – maledr53 Dec 15 '17 at 18:38
  • 1
    Its open the app but also redirect the browser to the appstore/playstore. Any method/hack to prevent this ? – Sujeet Kumar Dec 19 '19 at 10:41
  • @SujeetKumar Did you find any way to prevent the appstore from opening? – Nirvan Anjirbag Dec 23 '20 at 18:59
169

To further the accepted answer, you sometimes need to add extra code to handle people returning to the browser after launching the app - that the setTimeout function will run whenever they do. So, I do something like this:

var now = new Date().valueOf();
setTimeout(function () {
    if (new Date().valueOf() - now > 100) return;
    window.location = "https://itunes.apple.com/appdir";
}, 25);
window.location = "appname://";

That way, if there has been a freeze in code execution (i.e., app switching), it won't run.

Peter Mortensen
  • 28,342
  • 21
  • 95
  • 123
Alastair
  • 5,220
  • 6
  • 30
  • 55
  • 2
    This solution is much better than others, I had to increment the time on 50 – Magico May 30 '13 at 10:52
  • 1
    My comment about it failing on iOS 7 was false: I simply had to increase the 100 ms to a couple seconds (no need for it to be that short anyway really). – devios1 Oct 20 '13 at 21:26
  • Instead of https://itunes.apple.com/appdir, save a step (no waiting for an http redirect) and link directly to the app store URL scheme. For example: itms://itunes.apple.com/us/app/yelp/id284910350 – Justin Oct 14 '14 at 00:46
  • Awesome it's working fine for ios apps, same code redirect to on both urls at same time in android apps. have you do for android for opening apps & play store? – Mehul Dudhat Oct 18 '14 at 12:15
  • I wrote something that does this for twitter: https://github.com/omarish/tweetable and the principle should work for other deeplinks as well (facebook, pinterest, etc). enjoy! – omarish May 17 '15 at 04:06
  • Do I need to add any codes to my application too? cause this code opens many applications in iOS, but not my app – Reyraa Feb 03 '16 at 08:55
  • @alihaghighatkhah you need to add a URL scheme to your native iOS app. https://dev.twitter.com/cards/mobile/url-schemes – Westy92 Apr 04 '16 at 16:53
  • 9
    This consistently opens both my app and the app store, even at 50. iOS 12 – Josh Wolff Aug 21 '19 at 23:39
27

iOS Safari has a feature that allows you to add a "smart" banner to your webpage that will link either to your app, if it is installed, or to the App Store.

You do this by adding a meta tag to the page. You can even specify a detailed app URL if you want the app to do something special when it loads.

Details are at Apple's Promoting Apps with Smart App Banners page.

The mechanism has the advantages of being easy and presenting a standardized banner. The downside is that you don't have much control over the look or location. Also, all bets are off if the page is viewed in a browser other than Safari.

brainjam
  • 18,180
  • 7
  • 49
  • 78
  • 2
    But it doesn't show app banner if user haven't install it – TomSawyer Sep 07 '17 at 16:05
  • Neither Universal links or Smart banners work for me. I need to have multiple native app links to different apps. Each with a Check IF Installed, launch it, else go to the store. I also don't have control of the domains to upload ownership file to for universal links. So both options are outruled for me. Need a pure javascript solution. – FranticRock Jun 26 '19 at 15:02
20

As of 2017, it seems there's no reliable way to detect an app is installed, and the redirection trick won't work everywhere.

For those like me who needs to deep link directly from emails (quite common), it is worth noting the following:

  • Sending emails with appScheme:// won't work fine because the links will be filtered in Gmail

  • Redirecting automatically to appScheme:// is blocked by Chrome: I suspect Chrome requires the redirection to be synchronous to an user interaction (like a click)

  • You can now deep link without appScheme:// and it's better but it requires a modern platform and additional setup. Android iOS


It is worth noting that other people already thought about this in depth. If you look at how Slack implements his "magic link" feature, you can notice that:

  • It sends an email with a regular http link (ok with Gmail)
  • The web page have a big button that links to appScheme:// (ok with Chrome)
Sebastien Lorber
  • 79,294
  • 59
  • 260
  • 386
10

@Alistair pointed out in this answer that sometimes users will return to the browser after opening the app. A commenter to that answer indicated that the times values used had to be changed depending on iOS version.

When our team had to deal with this, we found that the time values for the initial timeout and telling whether we had returned to the browser had to be tuned, and often didn't work for all users and devices.

Rather than using an arbitrary time difference threshold to determine whether we had returned to the browser, it made sense to detect the "pagehide" and "pageshow" events.

I developed the following web page to help diagnose what was going on. It adds HTML diagnostics as the events unfold, mainly because using techniques like console logging, alerts, or Web Inspector, jsfiddle.net, etc. all had their drawbacks in this work flow. Rather than using a time threshold, the JavaScript counts the number of "pagehide" and "pageshow" events to see whether they have occurred. And I found that the most robust strategy was to use an initial timeout of 1000 (rather than the 25, 50, or 100 reported and suggested by others).

This can be served on a local server, e.g. python -m SimpleHTTPServer and viewed on iOS Safari.

To play with it, press either the "Open an installed app" or "App not installed" links. These links should cause respectively the Maps app or the App Store to open. You can then return to Safari to see the sequence and timing of the events.

(Note: this will work for Safari only. For other browsers (like Chrome) you'd have to install handlers for the pagehide/show-equivalent events).

Update: As @Mikko has pointed out in the comments, the pageshow/pagehide events we are using are apparently no longer supported in iOS8.

<html>
<head>
</head>

<body>
<a href="maps://" onclick="clickHandler()">Open an installed app</a>
<br/><br/>
<a href="xmapsx://" onclick="clickHandler()">App not installed</a>
<br/>

<script>
    var hideShowCount = 0 ;
    window.addEventListener("pagehide", function() {
        hideShowCount++;
        showEventTime('pagehide');
    });

    window.addEventListener("pageshow", function() {
        hideShowCount++;
        showEventTime('pageshow');
    });

    function clickHandler(){
        var hideShowCountAtClick = hideShowCount;
        showEventTime('click');
        setTimeout(function () {
                      showEventTime('timeout function ' + (hideShowCount-hideShowCountAtClick) + ' hide/show events');
                      if (hideShowCount == hideShowCountAtClick){
                              // app is not installed, go to App Store
                           window.location = 'http://itunes.apple.com/app';
                      }
                   }, 1000);
    }

    function currentTime()
    {
        return Date.now()/1000;
    }

    function showEventTime(event){
        var time = currentTime() ;
        document.body.appendChild(document.createElement('br'));
        document.body.appendChild(document.createTextNode(time + ' ' + event));
    }
</script>
</body>

</html>
Peter Mortensen
  • 28,342
  • 21
  • 95
  • 123
brainjam
  • 18,180
  • 7
  • 49
  • 78
9

You can check out this plugin that tries to solve the problem. It is based on the same approach as described by missemisa and Alastair etc, but uses a hidden iframe instead.

https://github.com/hampusohlsson/browser-deeplink

hampusohlsson
  • 9,346
  • 5
  • 30
  • 46
2

I needed to do something like this, and I ended up going with the following solution.

I have a specific website URL that will open a page with two buttons

  1. Button one go to the website

  2. Button two go to the application (iPhone / Android phone / tablet). You can fall back to a default location from here if the app is not installed (like another URL or an app store)

  3. Cookie to remember the user's choice

     <head>
         <title>Mobile Router Example </title>
    
    
         <script type="text/javascript">
             function set_cookie(name,value)
             {
                // JavaScript code to write a cookie
             }
             function read_cookie(name) {
                // JavaScript code to read a cookie
             }
    
             function goToApp(appLocation) {
                 setTimeout(function() {
                     window.location = appLocation;
                         // This is a fallback if the app is not installed.
                         // It could direct to an app store or a website
                         // telling user how to get the app
                 }, 25);
                 window.location = "custom-uri://AppShouldListenForThis";
             }
    
             function goToWeb(webLocation) {
                 window.location = webLocation;
             }
    
             if (readCookie('appLinkIgnoreWeb') == 'true' ) {
                 goToWeb('http://somewebsite');
    
             }
             else if (readCookie('appLinkIgnoreApp') == 'true') {
                 goToApp('http://fallbackLocation');
             }
    
         </script>
     </head>
    
     <body>
         <div class="iphone_table_padding">
         <table border="0" cellspacing="0" cellpadding="0" style="width:100%;">
             <tr>
                 <td class="iphone_table_leftRight">&nbsp;</td>
                 <td>
                     <!-- Intro -->
                     <span class="iphone_copy_intro">Check out our new app or go to website</span>
                 </td>
                 <td class="iphone_table_leftRight">&nbsp;</td>
             </tr>
             <tr>
                 <td class="iphone_table_leftRight">&nbsp;</td>
                 <td>
                     <div class="iphone_btn_padding">
    
                         <!-- Get iPhone app button -->
                         <table border="0" cellspacing="0" cellpadding="0" class="iphone_btn" onclick="set_cookie('appLinkIgnoreApp',document.getElementById('chkDontShow').checked);goToApp('http://getappfallback')">
                             <tr>
                                 <td class="iphone_btn_on_left">&nbsp;</td>
                                 <td class="iphone_btn_on_mid">
                                     <span class="iphone_copy_btn">
                                         Get The Mobile Applications
                                     </span>
                                 </td>
                                 <td class="iphone_btn_on_right">&nbsp;</td>
                             </tr>
                         </table>
    
                     </div>
                 </td>
                 <td class="iphone_table_leftRight">&nbsp;</td>
             </tr>
             <tr>
                 <td class="iphone_table_leftRight">&nbsp;</td>
                 <td>
                     <div class="iphone_btn_padding">
    
                         <table border="0" cellspacing="0" cellpadding="0" class="iphone_btn"  onclick="set_cookie('appLinkIgnoreWeb',document.getElementById('chkDontShow').checked);goToWeb('http://www.website.com')">
                             <tr>
                                 <td class="iphone_btn_left">&nbsp;</td>
                                 <td class="iphone_btn_mid">
                                     <span class="iphone_copy_btn">
                                         Visit Website.com
                                     </span>
                                 </td>
                                 <td class="iphone_btn_right">&nbsp;</td>
                             </tr>
                         </table>
    
                     </div>
                 </td>
                 <td class="iphone_table_leftRight">&nbsp;</td>
             </tr>
             <tr>
                 <td class="iphone_table_leftRight">&nbsp;</td>
                 <td>
                     <div class="iphone_chk_padding">
    
                         <!-- Check box -->
                         <table border="0" cellspacing="0" cellpadding="0">
                             <tr>
                                 <td><input type="checkbox" id="chkDontShow" /></td>
                                 <td>
                                     <span class="iphone_copy_chk">
                                         <label for="chkDontShow">&nbsp;Don&rsquo;t show this screen again.</label>
                                     </span>
                                 </td>
                             </tr>
                         </table>
    
                     </div>
                 </td>
                 <td class="iphone_table_leftRight">&nbsp;</td>
             </tr>
         </table>
    
         </div>
    
     </body>
    
     </html>
    
Peter Mortensen
  • 28,342
  • 21
  • 95
  • 123
nate_weldon
  • 2,239
  • 24
  • 32
  • I need this type of code for my requirement but unable to place my links. Requirement : If app1 is installed go to Link 1 , else goto Link2. Can someone please help? – SFDC_Learner Jun 22 '16 at 07:51
  • Need to unset the cookie when the app is installed, in case user 1st chooses web. – Charlie Reitzel Dec 13 '19 at 00:52
2

After compiling a few answers, I've come up with the following code. What surprised me was that the timer does not get frozen on a PC (Chrome and Firefox) or Android Chrome - the trigger worked in the background, and the visibility check was the only reliable information.

var timestamp        = new Date().getTime();
var timerDelay       = 5000;
var processingBuffer = 2000;

var redirect = function(url) {
  //window.location = url;
  log('ts: ' + timestamp + '; redirecting to: ' + url);
}

var isPageHidden = function() {
    var browserSpecificProps = {hidden:1, mozHidden:1, msHidden:1, webkitHidden:1};
    for (var p in browserSpecificProps) {
        if(typeof document[p] !== "undefined"){
          return document[p];
      }
    }
    return false; // Actually inconclusive, assuming not
}
var elapsedMoreTimeThanTimerSet = function(){
  var elapsed = new Date().getTime() - timestamp;
  log('elapsed: ' + elapsed);
  return timerDelay + processingBuffer < elapsed;
}

var redirectToFallbackIfBrowserStillActive = function() {
  var elapsedMore = elapsedMoreTimeThanTimerSet();
  log('hidden:' + isPageHidden() + '; time: ' + elapsedMore);
  if (isPageHidden() || elapsedMore) {
    log('not redirecting');
  }else{
      redirect('appStoreUrl');
  }
}

var log = function(msg){
    document.getElementById('log').innerHTML += msg + "<br>";
}

setTimeout(redirectToFallbackIfBrowserStillActive, timerDelay);
redirect('nativeApp://');

JS Fiddle

Peter Mortensen
  • 28,342
  • 21
  • 95
  • 123
ptrk
  • 1,684
  • 1
  • 14
  • 21
-2

The date solution is much better than others. I had to increment the time to 50 like that.

This is a Twitter example:

// On click of your event handler...
var twMessage = "Your Message to share";
var now = new Date().valueOf();
setTimeout(function () {
   if (new Date().valueOf() - now > 100) 
       return;
   var twitterUrl = "https://twitter.com/share?text=" + twMessage;
   window.open(twitterUrl, '_blank');
}, 50);
window.location = "twitter://post?message=" + twMessage;

The only problem on mobile iOS Safari is when you don't have the app installed on the device, and so Safari shows an alert that autodismisses when the new URL is opened. Anyway, it is a good solution for now!

Peter Mortensen
  • 28,342
  • 21
  • 95
  • 123
Magico
  • 2,806
  • 1
  • 13
  • 17
-5

I didn't read all of these answers, but you may be use an iframe and adding the source to, "my app://whatever".

Then check regularly on a set interval of the page is 404 or not.

You could also use an Ajax call. If there is a 404 response then the app is not installed.

Peter Mortensen
  • 28,342
  • 21
  • 95
  • 123
1.21 gigawatts
  • 12,773
  • 23
  • 85
  • 187