Skip to content

PWABuilder

PWABuilder is an open-source (MIT-licensed) project, hosted by Microsoft, that audits a deployed progressive web app and packages it for app stores. You give pwabuilder.com a URL. A server-side analyzer fetches the page, manifest and service worker, scores them on a report card, and then generates a Microsoft Store MSIX bundle, a Google Play Android App Bundle built on a Trusted Web Activity, or an experimental Xcode project for the Apple App Store. The same project maintains PWABuilder Studio for VS Code, the pwa-starter template and a CLI. This page covers what each part does in September 2026, how the checks actually work, and where PWABuilder's output needs your own engineering.

Key takeaways

  • The report card groups more than 50 checks into four categories (web app manifest, service worker, HTTPS, general) and four levels (Required, Recommended, Optional, Feature). Required checks include name, short_name, start_url, a square PNG icon of at least 192×192 with purpose any, fetchable icons with correct MIME types, no ICO or base64 images, HTTPS, and a text/html response.
  • Service worker features are detected by regular expressions over the worker source, including scripts pulled in with importScripts(). Offline support is tested with Puppeteer: load the page, wait for navigator.serviceWorker.ready, go offline and reload. PWABuilder used Lighthouse's offline audit until Lighthouse removed its PWA audits.
  • Packages: Windows gets an .msixbundle plus a .classic.appxbundle, and you need three identity values from Partner Center. Android gets a Bubblewrap-generated TWA (.aab, a test APK, assetlinks.json and a signing key) and requires Android 7.0 (API 24) or newer. iOS gets a Swift WKWebView project, which PWABuilder labels experimental.
  • The dedicated Meta Quest platform is gone. Its packaging form was removed from the site and its guide from the documentation (August 2026). What remains is a Meta Quest checkbox in the Android form that passes Bubblewrap's isMetaQuest flag. For a full Horizon Store workflow, use Meta's Bubblewrap fork.
  • Store packages load your live site, so web deploys don't need resubmission. Changes to the package (name, icons, signing key, version, capabilities) do.
  • The generated service worker templates import Workbox 5.1.2 from Google's CDN. Treat them as starting points, not production workers.

The PWABuilder family of tools

Tool What it is Where
pwabuilder.com URL analyzer, report card, manifest editor, service worker templates, image generator, store packaging apps/pwabuilder in the GitHub repository (ASP.NET Core backend, Lit/TypeScript frontend)
Packaging services Separate services build Microsoft Store and Google Play packages apps/pwabuilder-microsoft-store, apps/pwabuilder-google-play
PWABuilder Studio VS Code extension PWABuilder.pwa-studio, version 1.3.5 in the repository apps/pwabuilder-vscode
pwa-starter Lit + Vite starter template, now using Web Awesome components, vite-plugin-pwa 1.3 and Workbox 7.4 pwa-builder/pwa-starter repository
@pwabuilder/cli pwa create, pwa start and related commands for the starter (0.0.17 on npm) apps/cli
<pwa-install> Install-prompt web component @khmyznikov/pwa-install (0.7.0), listed by the project khmyznikov/pwa-install repository
Docs and blog docs.pwabuilder.com and blog.pwabuilder.com docs/, apps/blog

Everything is open source, so when this page describes the checks, it describes the code in the pwa-builder/PWABuilder repository as of September 2026. The code changes often: the repository received commits the week this page was written.

What happens when you enter a URL

PWABuilder's documentation describes its fetcher, the "PWABuilder bot", as user-initiated only. It visits a URL only when a user submits it, doesn't crawl on a schedule, doesn't revisit on its own, and states that it doesn't use fetched data to train AI models. Analyses run as queued jobs on the server and are stored, so the report card fills in progressively.

sequenceDiagram
    participant U as You (browser)
    participant PB as pwabuilder.com
    participant Q as Analysis job
    participant Site as Your site
    U->>PB: Submit https://app.example.com
    PB->>Q: Enqueue analysis
    Q->>Site: GET page (expects text/html over HTTPS)
    Q->>Site: GET manifest from link rel=manifest
    Q->>Site: GET icons, screenshots, shortcut icons
    Q->>Site: Headless Chromium: detect registered service worker
    Q->>Site: GET worker script + importScripts() URLs
    Q->>Site: Offline test: reload with network disabled
    Q-->>PB: Capabilities: Passed / Failed / Skipped
    PB-->>U: Report card, action items, Package for stores

Because the analysis happens on PWABuilder's servers, your site must be reachable from the public internet. Localhost, VPN-only staging and IP-allowlisted environments can't be analyzed, and bot protection that challenges headless browsers makes the service worker checks fail even when your worker is fine.

The report card, check by check

The report card shows three progress rings (manifest, service worker, HTTPS) and an Action Items list that you can filter by Required and Recommended. A site is considered store-ready when no Required check fails. The checks are defined in PwaCapability.cs.

Required checks

Check Category What it verifies
Has manifest Manifest A manifest is linked and parses as JSON
name Manifest Present
short_name Manifest Present
start_url Manifest Present
icons Manifest Present
Square 192×192+ PNG icon with purpose any Manifest At least one PNG of 192×192 or larger whose purpose includes any (or has no purpose)
Icons are fetchable Manifest Each icon URL responds with an image content type
Icon types are valid Manifest Declared type matches the real file type
Icon types are not ICO Manifest No .ico icons, because most stores reject them
Images are not base64-encoded Manifest No data: URLs in icons or screenshots
Shortcut icons are fetchable Manifest Every shortcuts[].icons[] URL loads
Has HTTPS HTTPS The site is served over HTTPS
Serves HTML General The URL returns text/html. The error names the type actually served.
Level Checks
Recommended description, background_color, theme_color, display, orientation, id, screenshots (and that they're fetchable, correctly typed and correctly sized), a square 512×512+ PNG any icon (store packaging needs it), declared icon sizes matching real dimensions, shortcut icon types and sizes, has a service worker, service worker is not empty
Optional categories, scope, scope_extensions, lang, dir, iarc_rating_id, related_applications, prefer_related_applications, display_override, a wide screenshot, a narrow screenshot
Feature shortcuts, share_target, file_handlers, protocol_handlers, launch_handler, widgets, edge_side_panel, note_taking, window controls overlay, tabbed display, periodic sync, background sync, push notifications, offline support

The manifest-level checks are also published as a library (libraries/manifest-validation), which PWABuilder Studio uses. Its rules differ slightly in wording, for example "Icons have at least one PNG icon 512x512 or larger" is a required rule there. Two points about the levels are easy to misread:

  • A service worker is only Recommended. PWABuilder can package a site without one, which is consistent with Chromium no longer requiring a fetch handler for installability (see Installability criteria). A store app without offline behavior is still a poor experience. The TWA quality criteria Google announced in 2020 (a failed offline request treated like a native crash from Chrome 86, a Lighthouse performance score of at least 80) are no longer enforced, since enforcement was removed in 2023 (Chromium 115), so treat them as historical guidance only. See Trusted Web Activity.
  • "Feature" checks are opportunities, not requirements. A red Feature item means only that the manifest member or the event listener wasn't found.

How the service worker checks actually work

ServiceWorkerAnalyzer.cs combines static analysis with one runtime test:

Capability How it's decided
Has service worker A worker registration was detected on the page in headless Chromium
Service worker is not empty Fails if the source matches an "empty fetch handler" pattern (for example addEventListener('fetch', e => e.respondWith(fetch(e.request))) with no .catch), or if it matches none of: importScripts or self., .addAll, or a fetch listener
Push notifications Regex for addEventListener("push" or onpush =
Background sync Regex for addEventListener("sync", onsync = or BackgroundSyncPlugin
Periodic sync Regex for addEventListener("periodicsync" or onperiodicsync =
Offline support Puppeteer loads the page, waits for 2 seconds of network idle (up to 20 seconds), waits up to 10 seconds for navigator.serviceWorker.ready, switches the page to offline mode, reloads, and passes only if the reload returns an OK response

The analyzer fetches the worker script and every URL it finds in importScripts() calls, and runs the regular expressions over the concatenated source. Code the worker loads any other way, such as static import statements in a module worker whose bundle doesn't contain the listener text, isn't seen. Minification that turns self.addEventListener("push", …) into an aliased call can also hide a listener. The offline test only checks the start page after a reload, so it passes for a worker that caches that one page and nothing else. It is also sensitive to anything that delays navigator.serviceWorker.ready past 10 seconds, such as Angular's default registerWhenStable:30000 strategy on an app that never stabilizes. Treat a failed offline check as "verify manually", and a passed one as necessary but not sufficient. The comment in the analyzer's source notes that PWABuilder used Lighthouse's offline audit until Lighthouse removed its PWA audits. See Auditing PWAs after Lighthouse dropped the PWA category.

What the report card doesn't check

The report card isn't a browser installability test and doesn't replace one. It doesn't evaluate Chrome's actual install criteria (for example, whether the manifest's start_url is within scope), Safari's Home Screen behavior, performance, accessibility, content security policy, or whether your offline experience works beyond the start page. Use the manifest pane in Chrome or Edge DevTools for installability errors (Browser DevTools) and your own tests for the rest (Lighthouse & auditing, Automated testing).

Editing the manifest on pwabuilder.com

Edit your manifest on the report card opens a form-based editor. PWABuilder's documentation describes six tabs:

Tab Covers
Info Display information: name, short_name, description, colors, categories
Settings Runtime behavior: start_url, scope, display, orientation, lang, dir
Platform OS and store integration: iarc_rating_id, related_applications, prefer_related_applications, shortcuts, protocol_handlers, file_handlers, share_target, display_override, widgets, edge_side_panel, and similar members
Icons Upload one image and generate a set of icons
Screenshots Generate screenshots from your deployed URL
Code Live preview of the resulting JSON

The editor edits a copy. Download manifest gives you the JSON, and you still have to deploy it and link it with <link rel="manifest">. PWABuilder's documentation suggests naming it manifest.json in the site root. Any name and path work, as long as the link is correct and the file is served with a JSON-compatible content type. For every member the editor exposes, the manifest members reference and Advanced & integration members explain the browser-side behavior that the editor's tooltips summarize. Packages are generated from the manifest PWABuilder fetched from your live site, not from unsaved edits, so deploy the edited manifest and re-run the analysis before you package.

The image generator

PWABuilder generates icon sets in two places: the Icons tab of the manifest editor, and a standalone page at https://www.pwabuilder.com/imageGenerator, which PWABuilder's source notes is also used by Edge DevTools. The standalone generator takes:

  • Input image. Upload a square image. 512×512 or larger avoids upscaling.
  • Padding. A fraction of the image size added around the artwork (default 0).
  • Background color. Transparent (default) or a custom color from a color picker.
  • Platforms. Windows 11, Android and iOS (all selected by default).

The server returns a ZIP with a folder of images per platform and an icons.json file whose icons array you paste into your manifest. Two cautions apply. The generator scales one image, so it can't produce a well-designed maskable icon, whose artwork must stay inside the central safe-zone circle, 40% of the icon size in radius. Design that variant separately and give it "purpose": "maskable". And Safari has its own Home Screen icon rules, so keep an apple-touch-icon link as well. See Icons & maskable icons and Splash screens & theming.

Service worker templates

When the report card finds no worker, it offers three downloadable templates. All three importScripts() Workbox 5.1.2 from storage.googleapis.com/workbox-cdn, and all three listen for a { type: "SKIP_WAITING" } message:

Template Behavior
Offline Pages Precaches one offline page at install time. Navigations use navigation preload or the network, and fall back to the offline page.
Offline Page Copy of Pages Registers a Workbox route for new RegExp('/*') with StaleWhileRevalidate, so every GET request goes into one cache as it's viewed
Offline Copy with Backup Offline Page The previous two combined

Read them before you deploy them:

  • Workbox 5.1.2 is from 2020. The current release is 7.4.1, and loading the runtime from Google's CDN adds a third-party script dependency to your worker. That dependency also conflicts with a strict script-src in the worker's Content Security Policy.
  • new RegExp('/*') matches every URL, including cross-origin ones. The regular expression matches an empty string at index 0, which Workbox accepts for cross-origin requests. So the "copy of pages" templates cache API responses, analytics beacons sent with GET, and third-party opaque responses, with no expiration. On a shared device, a stale-while-revalidate copy of an authenticated API response is visible to the next user.
  • The combined template's offline page never appears. Workbox's router registers its fetch listener when the route is registered, before the template's own listener. For navigations, the catch-all route calls respondWith() first, so the second listener's respondWith() throws InvalidStateError, and an uncached page fails instead of showing the offline page.
  • The offline page name is a placeholder. const offlineFallbackPage = "ToDo-replace-this-name.html" must be changed, or installation fails because cache.add() rejects with a 404.

For a production worker, start from the patterns on Caching strategies and Offline UX & fallbacks, or generate one with current Workbox as shown on Workbox fundamentals. This hand-written equivalent of "Offline Pages" has no third-party dependency:

sw.js
const CACHE = 'offline-v1';
const OFFLINE_URL = '/offline.html';

self.addEventListener('install', (event) => {
  // cache: 'reload' avoids precaching a stale HTTP-cached copy.
  event.waitUntil(caches.open(CACHE).then((c) => c.add(new Request(OFFLINE_URL, { cache: 'reload' }))));
});

self.addEventListener('activate', (event) => {
  event.waitUntil(
    (async () => {
      if (self.registration.navigationPreload) await self.registration.navigationPreload.enable();
      for (const key of await caches.keys()) if (key !== CACHE) await caches.delete(key);
      await self.clients.claim();
    })(),
  );
});

self.addEventListener('message', (event) => {
  if (event.data?.type === 'SKIP_WAITING') self.skipWaiting();
});

self.addEventListener('fetch', (event) => {
  if (event.request.mode !== 'navigate') return; // leave everything else to the network
  event.respondWith(
    (async () => {
      try {
        return (await event.preloadResponse) ?? (await fetch(event.request));
      } catch {
        return (await caches.match(OFFLINE_URL, { cacheName: CACHE })) ?? Response.error();
      }
    })(),
  );
});

Packaging for the Microsoft Store

The Windows package is an MSIX that registers your PWA with Windows, and Microsoft Edge runs it. PWABuilder produces two files: an .msixbundle for current Windows versions and a .classic.appxbundle for older ones. Upload both.

  1. Reserve the name in Partner Center. Go to Apps and games, choose New product > MSIX or PWA app, and reserve a name. Account types and fees changed in 2025–2026. See Publishing to app stores for the current onboarding flows.
  2. Copy the identity values. Under Product management > Product identity, copy the Package ID, Publisher ID and Publisher display name.
  3. Generate the package. On pwabuilder.com, choose Package for stores, then Generate Package under Windows, and fill in the form:

    Field Notes from the form
    Package ID, Publisher ID, Publisher display name From Partner Center; must match exactly
    App name Must equal the reserved name. A mismatch causes "This package's manifest uses a display name that you have not reserved".
    App version Format 1.0.0, can't start with zero, must be greater than the classic version. PWABuilder suggests 1.0.1 for new apps.
    Classic app version Format 1.0.0, must be less than the app version. PWABuilder suggests 1.0.0 for new apps.
    Icon URL, Icon background color Source for the generated Windows tile and logo images
    Language One or more package languages
    Device families Desktop and Holographic (HoloLens) are checked by default; Surface Hub (Team) is unchecked
    Enable Widgets Available only if the manifest has a widgets member. Serves those widgets to the Windows Widgets Board.
    Enable Actions Available only if the manifest has both share_target and protocol_handlers. Takes an ActionsManifest.json upload.
    Enable App URI Handler Lets the package handle links to your domain, and lets the site detect the installed package
  4. Test locally. Install the package from the ZIP on a Windows machine and check the Start menu entry, icons, and any file or protocol handlers.

  5. Submit. In the Partner Center submission, upload both bundles under Packages. Warnings about restricted capabilities such as runFullTrust and packageManagement are expected, and PWABuilder's documentation says they can be ignored. PWABuilder notes that review usually takes 24 to 48 hours.

Two runtime details from PWABuilder's FAQ are useful for analytics and UX. When the app is launched from the Store package, document.referrer is app-info://platform/microsoft-store on the first page (it's empty after a navigation or refresh). And you can open the Store's rating dialog by navigating to ms-windows-store://review/?ProductId=<your product ID>. To check from the website whether the Store package is installed, add a related_applications entry with "platform": "windows" and an ID of the form <PackageFamilyName>!<AppId>, then call navigator.getInstalledRelatedApps(). Detecting installed apps covers the API and its limits. PWABuilder's FAQ names a nonexistent navigator.getRelatedApplications() in one answer, so use the correct method name.

Packaging for Google Play and other Android stores

PWABuilder's Android packages are generated with Google's Bubblewrap and run your PWA in a Trusted Web Activity. Current packages require Android 7.0 (API level 24) or newer. PWABuilder's documentation says this minimum meets Google Play's automatic protection requirements, and that Android 6.0 is no longer supported by new packages. Google Play packaging runs as a queued job with its own status page on pwabuilder.com.

The Google Play tab of the Android dialog exposes these options:

Option Maps to / meaning
Package ID Android application ID, such as com.example.tasks. It's permanent once published.
App name, Short name (launcher name) Pre-filled from name and short_name. Google recommends 12 characters or fewer for the launcher.
Host, Start URL, Manifest URL Pre-filled from the manifest. The start URL is relative to the host.
Version, Version code android:versionName (a string) and android:versionCode (an integer that must increase with every upload)
Theme color, Theme dark color, Background color Status bar colors and the splash screen background
Nav color, Nav dark color, Nav divider color, Nav divider dark color Android navigation bar colors
Icon URL, Maskable icon URL, Monochrome icon URL 512×512 PNGs recommended. The monochrome icon is used for notifications and can serve as a themed icon.
Splash fade out duration (ms) Splash screen fade-out
Display mode Standalone, Fullscreen, or Fullscreen sticky (bars hidden; edge swipes are passed to the app)
Notification delegation Web notifications are shown as the Android app's notifications
Location delegation Geolocation permission is handled by the Android app
Google Play billing Enables the Digital Goods API with Play Billing for in-app purchases
Settings shortcut Adds a Settings long-press menu item for managing the app's storage
ChromeOS only Restricts the package to ChromeOS devices (Google Play tab only)
Meta Quest Sets Bubblewrap's isMetaQuest, adding Quest-specific metadata and features to the Android manifest (see below)
Fallback behavior Custom Tabs (default) or WebView when no TWA-capable browser is installed
Include source code Adds the generated Android project to the download
Signing key New (PWABuilder generates a keystore), Use mine (upload your keystore, alias and passwords; required for updates) or None (unsigned output that Play won't accept)

The download is a ZIP containing the .aab for upload, a signed .apk for side-loading tests, assetlinks.json, and, for a new key, signing.keystore plus signing-key-info.txt with the alias and passwords. Store the key files in a secret store. PWABuilder's documentation says you need them for every future version.

Digital Asset Links is where most launches go wrong. After the first upload, Google Play re-signs the app with its app signing key, so the fingerprint in the ZIP's assetlinks.json (your upload key) isn't the one devices see. Add the SHA-256 fingerprint from Play Console > Setup > App integrity > App signing to sha256_cert_fingerprints, and serve the file at https://<host>/.well-known/assetlinks.json:

.well-known/assetlinks.json
[
  {
    "relation": ["delegate_permission/common.handle_all_urls"],
    "target": {
      "namespace": "android_app",
      "package_name": "com.example.tasks",
      "sha256_cert_fingerprints": [
        "AB:CD:…:01",
        "12:34:…:EF"
      ]
    }
  }
]

The first fingerprint is your upload key, so side-loaded test builds verify. The second is Play's app-signing key, so store installs verify. If verification fails, the TWA shows a browser URL bar, or the app appears to crash on launch. Publishing to app stores has the debugging steps. PWABuilder's documentation also warns that PWAs on Android can't currently target children, and recommends an age rating of 13+ with a target audience of older users in the Play listing.

The Other Android tab produces the same kind of TWA package without Play-specific options, for stores such as Samsung Galaxy Store or for direct distribution. Every store build needs its own statement in assetlinks.json if it uses a different package name or signing key.

When a user opens your PWA from the Android app, document.referrer starts with android-app:// on the first navigation. PWABuilder's FAQ suggests using it for attribution. A ?source=twa query on the start URL is more reliable, because it survives the first navigation.

Packaging for the Apple App Store

PWABuilder labels iOS packaging experimental. Its documentation warns that App Store acceptance depends on the app's UI and UX and on its use of native capabilities such as push notifications and in-app purchase, not on the package itself. The package is a native Swift app that hosts your PWA in a WKWebView. You need a Mac with Xcode that supports iOS 17 or later, and an Apple Developer Program membership ($99 per year).

The iOS form asks for the App name, Bundle ID, URL, Image URL (a square PNG of 512×512 or larger, from which all iOS icon sizes are generated), Splash screen color, Progress bar color, Status bar color and Permitted URLs. Permitted URLs are extra hosts the web view may navigate to without leaving the app, such as login.microsoftonline.com for OAuth. Your own domain is included automatically.

  1. Unzip the download, run pod install in src, and open the .xcworkspace. Building the .xcodeproj fails. If CocoaPods reports a missing privacy manifest, run pod repo update and then pod update.
  2. Under Signing & Capabilities, remove every capability the app doesn't use. Unused entitlements are a common rejection reason.
  3. In the Apple Developer portal, create an App ID matching the Bundle ID (enable Associated Domains and Push Notifications if you use them), a distribution certificate, and an App Store Connect provisioning profile.
  4. Create the app record in App Store Connect, archive with Product > Archive for Any iOS Device (arm64), and upload with Distribute App > App Store Connect.
  5. Select the build and submit it for review.

The wrapper sets a cookie named app-platform with the value iOS App Store, so your server and client code can detect it. That's how you hide web-only flows, such as a checkout for digital goods that App Store rules require to go through in-app purchase. Push notifications in the wrapper use Firebase Cloud Messaging through native code: you uncomment the marked lines in AppDelegate.swift and follow FCM's iOS setup. Web Push inside the web view doesn't apply. StoreKit 2 in-app purchases require extra manual work based on PWABuilder's example repositories. The wrapper also has its own WKWebsiteDataStore, so storage and sign-in state aren't shared with Safari or with a Home Screen copy of the site. Publishing to app stores covers the review guidelines, App-Bound Domains and service workers inside WKWebView.

You can't build the package without macOS. PWABuilder's FAQ points to hosted CI with Xcode or rented remote Macs as workarounds.

Meta Quest: from a platform to an Android option

PWABuilder used to offer Meta Quest as a separate packaging platform: an Android TWA for Meta Quest Browser, with its own "Oculus" form in the packaging dialog. In the current code, the packaging dialog lists only Windows, Android and iOS. The separate form is gone, and pull request #6268 (merged 10 August 2026) removed the Meta Quest guide from the documentation, noting that "the platform has been removed". Older tutorials that show a Meta Quest tile are out of date.

Quest support didn't disappear completely. The Android form (both the Google Play and Other Android tabs) still has a Meta Quest → Enable checkbox, described in the form as making "your Android package … compatible with Meta Quest devices". It sets Bubblewrap's isMetaQuest option. In Google's Bubblewrap template, that flag adds the com.oculus.pwa.NAME, com.oculus.pwa.START_URL and com.oculus.pwa.SCOPE metadata entries, optional head-tracking and hand-tracking uses-feature declarations, and the com.oculus.permission.HAND_TRACKING permission to AndroidManifest.xml. PWABuilder keeps the minimum SDK at 24 whether the box is checked or not (Bubblewrap's own --metaquest CLI flag sets 23).

The checkbox produces a Quest-compatible APK or AAB, but it doesn't cover Meta's newer options, such as choosing between a 2D and an immersive WebXR app mode, or Horizon Billing. For those, use Meta's fork, @meta-quest/bubblewrap-cli, whose bubblewrap init --manifest=<url> --metaquest flow produces Horizon Store packages. See Meta Horizon Store (Meta Quest).

Listing on Store.app

PWABuilder's documentation also describes listing a PWA on Store.app, a free, open web app catalog with ratings and reviews. It's a listing, not a package: you create a developer account on Store.app, claim your domain, and fill in the listing. Store.app's prerequisites mirror PWABuilder's own manifest checks: name, short_name, icons, start_url and description must be present.

PWABuilder Studio for VS Code

PWABuilder Studio (PWABuilder.pwa-studio) brings the same checks and packaging into the editor. The extension adds a PWABuilder Studio view container with Dev Dashboard, Web Manifest, Service Worker and Publish Checklist panes, and these commands:

Command What it does
PWABuilder Studio: New PWA Clones pwa-starter
PWABuilder Studio: Generate Web Manifest / Choose existing Web Manifest Creates and links a manifest, or points the extension at yours
PWABuilder Studio: Generate Service Worker / Update Service Worker / Choose existing Service Worker Offers a basic or advanced worker. It installs the Workbox CLI and runs workbox wizard, then adds a navigator.serviceWorker.register() call to index.html.
PWABuilder Studio: Generate Icons / Generate Screenshots Icons from a 512×512 base image, with padding and background options, or screenshots from your deployed URL. Both are added to the manifest.
PWABuilder Studio: Add shortcuts, Add a file handler, Add share target, Add a protocol handler Scaffold the corresponding manifest members
PWABuilder Studio: Validate Runs the manifest validation rules
PWABuilder Studio: Publish To Web Sets the app's public URL (with Azure Static Web Apps guidance if you have none)
PWABuilder Studio: Package your PWA Calls PWABuilder's packaging APIs for the chosen store. The app must already be deployed.
Start Dev Server, Build for Production, Run Tests Starter-project conveniences

The extension also contributes code snippets, all prefixed with pwa, for badging, notifications, share, file handling and similar APIs. Check them before relying on them. The documented pwa-notification-request-permission snippet calls Notifications.requestPermission(), which throws a ReferenceError. The API is Notification.requestPermission(), and it must be called from a user gesture. See Notifications API.

pwa-starter and the CLI

pwa-starter is PWABuilder's opinionated template: Lit web components, the @thepassle/app-tools router, and Vite with vite-plugin-pwa. In its current package.json, the UI library is Web Awesome (the documentation still says Shoelace), with vite 8, vite-plugin-pwa 1.3, workbox-build and workbox-precaching 7.4, and TypeScript 7. The worker is an injectManifest worker that you extend. Vite PWA plugin documents the plugin behavior it inherits. @pwabuilder/cli wraps it: pwa create <name> clones the default template (-t basic gives a lighter, closer-to-vanilla variant), and pwa start runs Vite, passing extra arguments through --viteArgs. The npm release (0.0.17, June 2024) lags behind the repository.

Step by step: from a URL to three store packages

  1. Deploy the PWA publicly over HTTPS with a linked manifest. Make sure the page URL itself returns text/html: a redirect to a login page, a JSON response or a bot challenge fails the analysis.
  2. Run the report card at pwabuilder.com and clear every Required action item. Typical fixes are adding short_name, a 512×512 PNG any icon, correct type values, and replacing base64 icons with URLs.
  3. Fix Recommended items that stores care about. Set id, description and screenshots with both wide and narrow form factors (see Rich install UI), plus theme_color and background_color. Deploy, then re-run the analysis, because packaging uses the live manifest.
  4. Make offline behavior real. Replace the template worker, if you used one, with a tested worker. Verify offline navigation beyond the start page, because the report card doesn't.
  5. Windows: reserve the name, copy the three identity values, generate the package, test-install it, and upload both bundles.
  6. Android: generate with a new signing key, back up the keystore, upload the .aab, add Play's app-signing fingerprint to assetlinks.json, and verify that the URL bar disappears on a real device.
  7. iOS (optional, experimental): generate on a Mac, trim capabilities, add native value (push, purchases), and submit with review notes explaining the native features.
  8. Plan updates. Web changes ship instantly. For package changes, bump the Windows versions (app version greater than classic version) and the Android version code, reuse the same Android key (Use mine), and regenerate.

Limitations and gotchas

  • Public URL only. The analyzer and packagers run on PWABuilder's servers. Staging behind authentication, VPNs or IP allowlists can't be analyzed. For CI, use the open-source repository or Bubblewrap directly (Trusted Web Activity).
  • Heuristic checks. Service worker features are regex matches, and the offline test covers one reload of the start page. Both false passes and false failures happen. Don't use the score as a quality gate.
  • Snapshot packaging. Packages embed values from the manifest at generation time (names, colors, icons, start URL, handlers). Changing the manifest later doesn't change installed packages until you generate and ship a new version.
  • The Android package ID and signing key are permanent. Losing the keystore generated by PWABuilder means you can't update the app outside Play's upload-key reset process.
  • iOS is a WebView wrapper, not a PWA runtime. It has no Web Push, uses separate storage, and depends on App Store review under guideline 4.2 (minimum functionality).
  • Removed targets. The separate Meta Quest platform is gone, and only the Android form's Meta Quest checkbox remains. Older blog posts and videos still show the old tile.
  • Templates and documentation lag behind the code. The service worker templates use Workbox 5.1.2, the starter documentation mentions Shoelace, and some FAQ snippets have wrong API names. When the documentation and the site disagree, the site's behavior, which comes from the repository, wins.

Further reading

On this site

External references