Forum Academy Marketplace Showcase Pricing Features

Can someone make a "Add to homescreen button"

Hey all can someone make a add to homescreen plugin??



Are you looking for just the button, or are you looking for a pwa option?

Here’s a pwa example on Android:

On IOS, it doesn’t support the prompt to the user, but if they add to home screen, it still uses the pwa function.

And on Desktop:


I make mostly mobile apps I’d like to prompt the user to install to home screen at a certain point… say 5 minutes after using the app for the first time

Hey Lantz,

Do you know how to do that on Bubble? I’ve seen the WebDev documentation, and built the manifest and sw,js but where do you paste the Java? text in Bubble?


Promoting installation #

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

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

Listen for the beforeinstallprompt event #

If your Progressive Web App meets the required installation criteria, the browser fires a beforeinstallprompt event. Save a reference to the event, and update your user interface to indicate that the user can install your PWA. This is highlighted below.

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();});

Hey @mccjon, It’s been a while so forgive me if I’m rusty. The script usually goes in your SEO / Metatags Body section located in your settings I believe. (I did it a bit different than your docs)

I managed to pull up old docs that I had for the PWA Creator that I had up a while back. Feel free to use any of it:

Script for Body:

if ("serviceWorker" in navigator) {
  if (navigator.serviceWorker.controller) {
    console.log("[PWA Builder] active service worker found, no need to register");
  } else {
    // Register the service worker
      .register("pwabuilder-sw.js", {
        scope: "./"
      .then(function (reg) {
        console.log("[PWA Builder] Service worker has been registered for scope: " + reg.scope);

JS file:

//verion 0.7

var manUpObject;

//data objects

var tagArray = [], linkArray = [];

var validMetaValues = [{ name: 'mobile-web-app-capable', manifestName: 'display' }, { name: 'apple-mobile-web-app-capable', manifestName: 'display' }, { name: 'apple-mobile-web-app-title', manifestName: 'short_name' }, { name: 'application-name', manifestName: 'short_name' }, { name: 'msapplication-TileColor', manifestName: 'ms_TileColor' }, { name: 'msapplication-square70x70logo', manifestName: 'icons', imageSize: '70x70' }, { name: 'msapplication-square150x150logo', manifestName: 'icons', imageSize: '150x150' },{ name: 'msapplication-wide310x150logo', manifestName: 'icons', imageSize: '310x150' },{ name: 'msapplication-square310x310logo', manifestName: 'icons', imageSize: '310x310' }];

var validLinkValues = [{ name: 'apple-touch-icon', manifestName: 'icons', imageSize: '152x152' },{ name: 'apple-touch-icon', manifestName: 'icons', imageSize: '120x120' },{ name: 'apple-touch-icon', manifestName: 'icons', imageSize: '76x76' },{ name: 'apple-touch-icon', manifestName: 'icons', imageSize: '60x60' },{ name: 'apple-touch-icon', manifestName: 'icons', imageSize: '57x57' }, { name: 'apple-touch-icon', manifestName: 'icons', imageSize: '72x72' }, { name: 'apple-touch-icon', manifestName: 'icons', imageSize: '114x114' }, { name: 'icon', manifestName: 'icons', imageSize: '128x128' }, { name: 'icon', manifestName: 'icons', imageSize: '192x192' }]

//these next two classes are building the mixed data, pulling out the values we need based on the valid values array

var generateFullMetaData = function(){

 for (var i = 0;i < validMetaValues.length;  i++) {


      if(validMetaValues[i].manifestName == 'icons'){

          //here we need to loop through each of the images to see if they match

          var imageArray = manUpObject.icons;

          for (var j = 0;j < imageArray.length;  j++) {

              if(imageArray[j].sizes == validMetaValues[i].imageSize){

                //problem is in here, need to asign proper value

                validMetaValues[i].content = imageArray[j].src;




          validMetaValues[i].content = manUpObject[validMetaValues[i].manifestName];

          if (validMetaValues[i].manifestName == 'display' && manUpObject.display == 'standalone') validMetaValues[i].content = 'yes'

         // console.log('stop')





 return validMetaValues


var generateFullLinkData = function () {

    for (var i = 0; i < validLinkValues.length; i++) {

        if (manUpObject[validLinkValues[i].manifestName]) {

            if (validLinkValues[i].manifestName == 'icons') {

                //here we need to loop through each of the images to see if they match

                var imageArray = manUpObject.icons;

                for (var j = 0; j < imageArray.length; j++) {

                    if (imageArray[j].sizes == validLinkValues[i].imageSize) {

                        //problem is in here, need to asign proper value

                        validLinkValues[i].content = imageArray[j].src;



            } else {

                validLinkValues[i].content = manUpObject[validLinkValues[i].manifestName];





    return validLinkValues


        //These are the 2 functions that build out the tags

        //TODO: make it loop once instead of tx

var generateMetaArray = function () {

    var tempMetaArray = generateFullMetaData();

    var headTarget = document.getElementsByTagName('head')[0]

    for (var i = 0; i < tempMetaArray.length; i++) {

        var metaTag = document.createElement('meta'); = tempMetaArray[i].name;

        metaTag.content = tempMetaArray[i].content;




var generateLinkArray = function () {

    var tempLinkArray = generateFullLinkData();

    var headTarget = document.getElementsByTagName('head')[0]

    for (var i = 0; i < tempLinkArray.length; i++) {

        var linkTag = document.createElement('link');

        linkTag.setAttribute('rel', tempLinkArray[i].name);

        linkTag.setAttribute('sizes', tempLinkArray[i].imageSize);

        linkTag.setAttribute('href', tempLinkArray[i].content);





//these functions makes the ajax calls and assigns to manUp object

var generateObj = function (ajaxString) {

    //for testing

    //document.getElementById("output").innerHTML = ajaxString;

    //gen object

    manUpObject = JSON.parse(ajaxString);




var makeAjax = function (url) {

    if (!window.XMLHttpRequest) return;

    var fullURL;

    var pat = /^https?:\/\//i;

    pat.test(url)?fulURL = url:fullURL = window.location.hostname + url;

    var ajax = new XMLHttpRequest();   

    ajax.onreadystatechange = function () {

        if (ajax.readyState == 4 && ajax.status == 200) generateObj(ajax.responseText)

    };"GET", url, true);



var collectManifestObj = function () {

    var links = document.getElementsByTagName('link');

    for (var i = 0; i < links.length; i++) {

        if (links[i].rel && links[i].rel == 'manifest') makeAjax(links[i].href);



var testForManifest = function () {

    //i'm not sure what to do here.  I am starchly against browser sniffing and there is no test (yet)

    //I think I'm jsut going to build out the tags on every page until there is a way to test it, so far

    //it looks like the manifest will override the tags

    //browser: we need context here



PWA Builder:

// This is the service worker with the Cache-first network

const CACHE = "pwabuilder-precache";

const precacheFiles = [

  /* Add an array of files to precache for your app */


self.addEventListener("install", function (event) {

  console.log("[PWA Builder] Install Event processing");

  console.log("[PWA Builder] Skip waiting on install");


  event.waitUntil( (cache) {

      console.log("[PWA Builder] Caching pages during install");

      return cache.addAll(precacheFiles);




// Allow sw to control of current page

self.addEventListener("activate", function (event) {

  console.log("[PWA Builder] Claiming clients for current page");



// If any fetch fails, it will look for the request in the cache and serve it from there first

self.addEventListener("fetch", function (event) { 

  if (event.request.cache === 'only-if-cached' && event.request.mode !== 'same-origin')


  if (event.request.method !== "GET") return;



      function (response) {

        // The response was found in the cache so we responde with it and update the entry

        // This is where we call the server to get the newest version of the

        // file to use the next time we show view


          fetch(event.request).then(function (response) {

            return updateCache(event.request, response);



        return response;


      function () {

        // The response was not found in the cache so we look for it on the server

        return fetch(event.request)

          .then(function (response) {

            // If request was success, add or update it in the cache

            event.waitUntil(updateCache(event.request, response.clone()));

            return response;


          .catch(function (error) {

            console.log("[PWA Builder] Network request failed and no cache." + error);






function fromCache(request) {

  // Check to see if you have it in the cache

  // Return response

  // If not in the cache, then return

  return (cache) {

    return cache.match(request).then(function (matching) {

      if (!matching || matching.status === 404) {

        return Promise.reject("no-match");


      return matching;




function updateCache(request, response) {

  return (cache) {

    return cache.put(request, response);



Bonus if you want it, this would go in your SEO / Metatags in your Header section in the settings:

<meta name="viewport" content="viewport-fit=cover, width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
<link rel="manifest" href="/manifest.json">
<link rel="manifest" href="/manifest.webmanifest">
<link rel="apple-touch-icon" sizes="152x152" href="https://YOURUPLOADEDIMAGE.png">
<link rel="apple-touch-icon" sizes="120x120" href="https://YOURUPLOADEDIMAGE.png">
<link rel="apple-touch-icon" sizes="76x76" href="https://YOURUPLOADEDIMAGE.png">
<link rel="apple-touch-icon" sizes="60x60" href="https://YOURUPLOADEDIMAGE.png">
<link rel="apple-touch-icon" sizes="57x57" href="https://YOURUPLOADEDIMAGE.png">
<link rel="apple-touch-icon" sizes="72x72" href="https://YOURUPLOADEDIMAGE.png">
<link rel="apple-touch-icon" sizes="114x114" href="https://YOURUPLOADEDIMAGE.png">
<link rel="icon" sizes="128x128" href="https://YOURUPLOADEDIMAGE.png">
<link rel="icon" sizes="192x192" href="https://YOURUPLOADEDIMAGE.png">
<meta name="mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-title" content="YOUR APP NAME">
<meta name="application-name" content="YOUR APP NAME">
<meta name="msapplication-TileColor" content="undefined">
<meta name="msapplication-square70x70logo" content="undefined">
<meta name="msapplication-square150x150logo" content="undefined">
<meta name="msapplication-wide310x150logo" content="undefined">
<meta name="msapplication-square310x310logo" content="undefined">

Thanks so much for that!

It’s quite technical (I’m evidently not :slight_smile: ) but will give it a go!

Thanks again!

1 Like

I might open this service back up in the future, but here is the documentation I wrote for it, if it helps visually… :slight_smile:

1 Like

Yes! That service would be great and hugely simplify the process

1 Like

Hi @Iantzgould… can’t wait for you to open the service again

Will you be re-opening this service ? Could really do with this, alternatively if you’d be happy to show me how to do this on a zoom call i’m happy to pay.


+1 for the service ! Why was it cancelled ?

1 Like

Hi :smiley:

Did you ever open up the service again?

This would be really useful. I would pay for that.