I have a PWA app that correctly prompts users to add the app to their home screen. The problem is that IMHO this automatic behavior is bad user experience because 90% of the time, the prompt is perceived like an annoying popup and users just close it.

What I think is way better UX, would be for users to look for a way to "install the app", like in the footer of any page where you usually see "Download on Android" or "Get the app in the App Store" or, after some specific interactions some popup I CONTROL would appear WHEN I FEEL the user is sufficiently engaged with my app and might be interested into saving it to its home screen.

I have done some searches and I have not been able to find any way to trigger the "add to home screen" popup when I need to. Did I miss something? How do you prompt for "add to homescreen" a second or third time?

Looking for any solution on Android and iPhone.

From Provide a custom install experience:

To indicate your Progressive Web App is installable, and to provide a custom in-app install flow:

  1. Listen for the beforeinstallprompt event.
  1. Save the beforeinstallprompt event, so it can be used to trigger the install flow later.
  2. Alert the user that your PWA is installable, and provide a button or other element to start the in-app installation flow.
let deferredPrompt;

window.addEventListener('beforeinstallprompt', (e) => {
  // Prevent the mini-infobar from appearing on mobile
  e.preventDefault();
  // Stash the event so it can be triggered later.
  deferredPrompt = e;
  // Update UI notify the user they can install the PWA
  showInstallPromotion();
});

Then markup the install option and handle user events to return the deferred prompt

buttonInstall.addEventListener('click', (e) => {
  // Hide the app provided install promotion
  hideMyInstallPromotion();
  // Show the install prompt
  deferredPrompt.prompt();
  // Wait for the user to respond to the prompt
  deferredPrompt.userChoice.then((choiceResult) => {
    if (choiceResult.outcome === 'accepted') {
      console.log('User accepted the install prompt');
    } else {
      console.log('User dismissed the install prompt');
    }
  })
});