DEV Community

Cover image for Shipping PWAs as Chrome Extensions
Sam Thorogood for Google Web Dev

Posted on • Updated on

Shipping PWAs as Chrome Extensions

Progressive Web Apps, or PWAs, are basically a type of website that works offline and may include native app-like features. But realistically, the PWA moniker is something you tell your boss, your investor, whoever, so they can conceptualize this web thing that you're building.

So you've built a PWA, created your service worker, and followed all the guides. In my case, that is Emojityper: a simple PWA where you can enter words, and receive emoji. This is perfect for desktop and entering emoji in editors that don't support them. ⌨️🔜🤣

Emojityper Chrome Extension showing its browser action

But once you've built this great experience, you're not limited to distributing it only on the 'web'. In this post, I'm going to detail how I shipped Emojityper as a Chrome extension, accessible via a browser action.

Creating an extension for your PWA is incredibly simple. Why? Because if your PWA already works offline, you can leverage the fact that Chrome is already caching it for your users. We're literally just building a small embedder that includes your site in an iframe. Let's start! 🏁

Extension Files

There's basically two files that make up our Chrome extension. First, the manifest.json file, describing some metadata:

  "name": "Emojityper",
  "version": "0.2",
  "description": "Type emoji on your keyboard!",
  "browser_action": {
    "default_popup": "embedder.html",
    "default_title": "Emojityper",
    "default_icon": {"128": "i128.png", "32": "i32.png", "16": "i16.png"}
  "icons": {"128": "i128.png", "32": "i32.png", "16": "i16.png"}
  "manifest_version": 2
Enter fullscreen mode Exit fullscreen mode

This manifest could also request additional permissions for features that are available to Chrome extensions. However, as we're just embedding a PWA that already takes advantage of the web platform's features—you should try to avoid it.

And next, the embedder.html file, which embeds your PWA in an iframe:

<!DOCTYPE html>
  body {
    background: #2c3e50;
    margin: 0;
    width: 380px;
    height: 200px;
  iframe {
    width: 100%;
    height: 100%;
    border: 0;
  <iframe id="iframe" src=""></iframe>
Enter fullscreen mode Exit fullscreen mode

Save these two files 💾⬇️📂 in a single folder (along with some icons—i16.png, i32.png, i128.png, or maybe fewer if you can get away with it), then head to chrome://extensions, enable Developer mode, and select "load unpacked extension".

Extension development

Great! You'll now see the Emojityper app appear in your extension bar and clicking on it will bring up the display we saw earlier.


Your PWA might just start working well. You've embedded into a handy popup that's hopefully helpful for your users. But there's two nuances.

Responsive Design

Our extension lives in an area 380 pixels wide by 200 pixels high. You can make this larger, of course, but the point is to provide a conveniently small UI. And while browser tabs can also be shrunk to this size—on my Mac, the smallest Chrome window I can make is 400 wide by 159 high—your users might expect to use your PWA on their desktop as say, a small music controller or other 'widget'. 🎶🔁

This all basically means that you might have to add a "micro" mode to your responsive design. Emojityper went from cluttered to simple by adding CSS for heights of 400px or less:

Clutter to simple

Extension Perks

The PWA we've embedded just works, because you're leveraging the web platform. But there'll always be things that an extension can do that your PWA can not.

In Emojityper's case, hitting the "copy" button should actually close the extension window. This is implemented by having the PWA look for its 'parent': the container/embedding page, and letting the parent know when copy was done. Inside Emojityper, I do something like this:

function notifyParentCopy() {
  // notify parent (for ext)
  if (window.parent) {
    window.parent.postMessage('copy', '*');
Enter fullscreen mode Exit fullscreen mode

And in the extension, I add an included JS file which listens for messages and takes action:

window.addEventListener('message', (ev) => {
  switch ( {
  case 'copy':
    console.warn('got unhandled message',;
Enter fullscreen mode Exit fullscreen mode

(Because our extension isn't actually hosted on a domain, it can just communicate with the embedded iframe without any security challenges.)

Is it worth detecting 'extension mode' and providing additional features? That's up to you. This article is mostly about bringing your PWA to where your users are, so I'd argue that only a bare minimum of additional features might make sense (in the same way that you probably don't need to request any permissions).

Thanks for reading! I hope you've learned a bit more about Emojityper and its Chrome extension. 😂

You could take a similar approach to include your PWA inside an Android, iOS or desktop native app via a web view. Your PWA can extend its distribution, and go where your users want to be. 🔜👥👏👩‍💻👨‍💻

Top comments (3)

jrohatiner profile image

Thanks! So helpful!

ethan profile image
Ethan Stewart • Edited

This is awesome, I'd never even considered using a PWA for an extension. Love it!

samthor profile image
Sam Thorogood

It works surprisingly well—all the cache, application etc is all shared.