Ian J MacIntosh.com

Make Your Web App Installable

I made my web app installable on Android and iOS devices by adding a web app manifest to my site. You can use mine as a reference if you’d like: https://gasco.st/gascost.webmanifest

Before you start, I recommend adding a test to verify you’ve provided visitors with the info they need to install your web app. Here’s mine, using Playwright:

import { test, expect } from "@playwright/test";

interface ManifestJson {
  name?: string;
  short_name?: string;
  icons?: { [sizes: string]: string }[];
  start_url?: string;
  display?: string;
}

const homepage = "/";

test.describe("Web app manifest", () => {
  test("satisfies Google Chrome's criteria", async ({ page, request }) => {
    await page.goto(homepage);

    const manifestLink = page.locator("link[rel='manifest']");
    await expect(manifestLink).toHaveCount(1);

    const manifestUrl = (await manifestLink.getAttribute("href")) || "";
    expect(manifestUrl).not.toBe("");

    const response = await request.get(manifestUrl);
    const manifestJson: ManifestJson = await response.json();

    expect("name" in manifestJson || "short_name" in manifestJson).toBeTruthy();
    expect(manifestJson).toHaveProperty("icons");
    expect(
      manifestJson.icons?.some(
        ({ sizes, type }) => sizes.includes("192x192") && type === "image/png",
      ) &&
        manifestJson.icons?.some(
          ({ sizes, type }) =>
            sizes.includes("512x512") && type === "image/png",
        ),
    ).toBeTruthy();
    expect(manifestJson).toHaveProperty("start_url");
    expect(manifestJson).toHaveProperty("display");
    expect([
      "standalone",
      "minimal-ui",
      "fullscreen",
      "windows-control-overlay",
    ]).toContain(manifestJson.display);
  });
});

That test does some checks for things I didn’t know I needed, and would have saved me some time! But it doesn’t check for HTTPS, which is required for an installable web app. During local development and testing, my page is served over HTTP (without TLS), so I skip that test to avoid false negatives.

For implementation, I created a web manifest document which is just a JSON document, served with MIME type application/manifest+json, with required values:

That file lives in my app’s static passthrough directory (“public”) and my homepage references it:

<link rel="manifest" href="/gascost.webmanifest">

My homepage also signals to old versions of Safari (iOS) that my web app is installable with these weirdo elements from 2016:

<link rel="apple-touch-icon" href="/gas_pump_512-min.png"> <!-- App icon for the Home screen -->
<meta name="apple-mobile-web-app-title" content="Gas Co.st"> <!-- App name for the Home screen -->
<meta name="mobile-web-app-capable" content="yes"> <!-- Hide browser chrome, display like a standalone app -->
<meta name="apple-mobile-web-app-status-bar-style" content="default"> <!-- Use system settings for color scheme -->

I tested and confirmed Safari in Oct 2025 prefers the web app manifest's short_name over the apple-mobile-web-app-title meta tag for the app name, but I'm not sure about priority order for those other elements.

To test my app manifest, I used my browser’s DevTools Application tab and went to the Manifest screen. Over the course of a couple of hours, I went crazy trying to figure out why my SVG image wouldn’t load. I was checking for inaccurate MIME types, restrictive CORS headers, and other theories that led nowhere.

Web Manifest DevTools

Figure 1: DevTools > Application > Manifest -- See the “Icon https://gasco.st/gas_pump.svg failed to load” error. You can see a PNG version in the bottom of the window now, but I didn’t have that when I was banging my head against the wall wondering why my SVG wasn’t loading.

Eventually I concluded that SVG’s just aren’t supported for PWA icons. You must have a PNG fallback. I only had my app icon in SVG format, but I used these free tools to get PNG’s:

I also was able to use https://applogoviewer.com/ to see that my icon needed some tweaking to look right on iOS and Android; my first icon attempt had a border thing around it that looked broken on iOS. I was able to test my fix without having to redeploy!

I added an application description and screenshots (in wide form factor for TV-shaped screens and undefined for everyone else) to provide users with an enhanced install experience. See below. I only have one screenshot, but I could add a bunch to make a little mini-carousel thing like you see in the Google Play Store and App Store.

Enhanced PWA Install Prompt

Figure 2: Brave Browser dialog window showing a rich installation prompt, with the app’s name, icon, description, and a preview screenshot

After making these changes, my app offers a way more polished experience for folks wanting to check gas prices. I shouldn’t have put this work off for so long -- implementing it took a few hours, most of which was chasing red herrings involving the SVG not loading. The pay-off of seeing my modest web app alongside native apps on my phone’s home screen is wonderful.

References

https://gasco.st/gascost.webmanifest

https://www.w3.org/TR/appmanifest/

https://web.dev/articles/install-criteria

https://web.dev/articles/add-manifest

https://developer.apple.com/library/archive/documentation/AppleApplications/Reference/SafariWebContent/ConfiguringWebApplications/ConfiguringWebApplications.html