Turn the chat app you built into something with a real icon on a real phone’s home screen — and get it onto the Google Play Store.
A Progressive Web App, or PWA, is a website that can be installed like a regular app — with its own icon on the home screen, its own window with no browser bar, and the ability to work as a real Android app through the Google Play Store. The website you already built doesn’t need to be rebuilt for this. You're adding a small set of files that tell phones and app stores how to treat it.
💡 You need exactly three things: a manifest.json file describing your app, a service worker file that lets it work offline, and two icon images. That’s the entire technical requirement.
You need two square PNG images: icon-192.png (192×192 pixels) and icon-512.png (512×512 pixels). Ask an AI chatbot to design a simple logo for your app, or create one yourself in any image editor. Keep it simple — a single bold shape or letter reads better at small sizes than a detailed illustration.
Extend the Worker from Tutorial 4 with four new routes. Add these constants near the top of your file:
const MANIFEST = { name: "My App", short_name: "My App", start_url: "/", display: "standalone", background_color: "#0d1117", theme_color: "#0d1117", icons: [ { src: "/icon-192.png", sizes: "192x192", type: "image/png", purpose: "any" }, { src: "/icon-192.png", sizes: "192x192", type: "image/png", purpose: "maskable" }, { src: "/icon-512.png", sizes: "512x512", type: "image/png", purpose: "any" }, { src: "/icon-512.png", sizes: "512x512", type: "image/png", purpose: "maskable" } ] };
⚠️ This exact detail caused a full day of debugging on a real PrivateAI site. Each icon size needs two separate entries — one with purpose "any" and one with purpose "maskable" — four entries total for two icon sizes. Combining them into one entry with "any maskable" looks like it should work, but Google’s packaging tool will reject it. Copy the structure above exactly.
For the icon images themselves, convert each PNG to a long line of text called base64, then add routes that decode and serve them:
if (url.pathname === "/manifest.json") { return new Response(JSON.stringify(MANIFEST), { headers: { "Content-Type": "application/manifest+json" } }); } if (url.pathname === "/icon-192.png") { const bin = atob(ICON192_BASE64); const bytes = new Uint8Array(bin.length); for (let i = 0; i < bin.length; i++) bytes[i] = bin.charCodeAt(i); return new Response(bytes, { headers: { "Content-Type": "image/png" } }); }
Add a simple service worker too — this is what makes the app installable:
const SW = "self.addEventListener('install', e => self.skipWaiting());" + "self.addEventListener('fetch', e => e.respondWith(fetch(e.request)));"; if (url.pathname === "/sw.js") { return new Response(SW, { headers: { "Content-Type": "application/javascript" } }); }
Finally, add these two lines inside the <head> of your HTML so the browser knows the manifest exists:
<link rel="manifest" href="/manifest.json"> <meta name="theme-color" content="#0d1117">
Deploy, then check yourworker.workers.dev/manifest.json in your browser — you should see the JSON with four icon entries.
Visit pwabuilder.com and enter your site’s URL (your custom domain, or the .workers.dev address). It will analyze your manifest, icons, and service worker.
If it flags an issue with your icons or manifest, it’s almost always the four-entry icon structure from Step 2. Re-check that before anything else.
Choose Google Play as the target, set a package name in reverse-domain format (like com.yourcompany.yourapp), and generate the package.
⚠️ PWABuilder’s servers are sometimes unreliable and will occasionally return a 500 error that has nothing to do with your site. If this happens, simply wait a few minutes and try again, or try a different browser. This is not a sign anything is wrong with your manifest.
When the package finishes, you’ll get a .zip file containing your .aab file (the Android app itself) and a signing keystore file.
⚠️ Save the keystore file somewhere safe immediately. This file is what proves you are the legitimate publisher of your app. Every future update to your app must be signed with this exact same keystore. If you lose it, you cannot update your app — ever. You would have to publish it as a brand new app instead.
Go to play.google.com/console and sign up. There is a one-time $25 registration fee.
Click Create app, give it a name, choose App and Free.
Go to Testing → Internal testing → Create new release. Upload your .aab file, add release notes, and add at least one tester email — your own is fine to start.
Fill in your app’s description, upload a feature graphic (1024×500 pixels) and at least two screenshots, complete the content rating questionnaire, and add a privacy policy URL.
🎉 Internal testing is instant. Once a tester accepts the invite, they can install your app from a private link — no waiting for Google’s review. Full public review is only required when you move to production.