Integrate PWAs into built-in sharing UIs with Workbox

How to get your PWA to show up next to platform-specific apps in system-level sharing UIs

The Web Share Target API lets you display your Progressive Web App in a user's system-level share sheet after it's been installed. While it works great if you have a server available to receive the request, it's much harder to get working if you don't.

In this article we'll use Workbox, a set of JavaScript libraries for adding offline support to web apps, to create a share target URL that lives entirely inside your service worker. This lets static sites and single-page apps serve as share targets without a dedicated server endpoint.

Android phone with the 'Share via' drawer open.
System-level share target picker with an installed PWA called Share Target Test as an option.

On the same page

If you're unfamiliar with how Web Share Target Works, Receiving shared data with the Web Share Target API gives you an in-depth introduction. Here's a quick review.

There are two parts to implementing web share target functionality. First, update your web app manifest to indicate that you want your app to be a share target when installed. The following example directs shares to the /share url via a POST request. It is encoded as a multipart form, with title being called name, text being called description, and JPEG images being called photos.

…
"share_target": {
  "action": "/share",
  "method": "POST",
  "enctype": "multipart/form-data",
  "params": {
    "title": "name",
    "text": "description",
    "files": [
      {
        "name": "photos",
        "accept": ["image/jpeg", ".jpg"]
      }
    ]
  }
}
…

Service worker share targets with Workbox

While normally handled by a server endpoint, a neat trick you can do for a share target is to register a route directly in your service worker to handle the request. This will let your app be a share target without a backend.

You do this in Workbox by registering a route that's handled by your service worker. Start by importing registerRoute from 'workbox-routing'. Notice that it's registered for the /share route, the same one listed in the example web app manifest. In response it calls shareTargetHandler().

import { registerRoute } from 'workbox-routing';

registerRoute(
  '/share',
  shareTargetHandler,
  'POST'
);

The shareTargetHandler() function is asynchronous and takes the event, awaits the form data, then retrieves the media files from that.

async function shareTargetHandler ({event}) {
  const formData = await event.request.formData();
  const mediaFiles = formData.getAll('media');

  for (const mediaFile of mediaFiles) {
    // Do something with mediaFile
    // Maybe cache it or post it back to a server
  });

  // Do something with the rest of formData as you need
  // Maybe save it to IndexedDB
};

You can then do whatever you'd like with these files. You can cache them. You can send them somewhere with a fetch request. You can even use the other manifest options, maybe serving a page with some query parameters for the other shared items or storing the data and pointers to the media in the Cache Storage API or IndexedDB.

You can try it out on the sample app Fugu Journal and see its service worker implementation in its source code.

One common thing you might do is hold shared resources until better network connections are available. Workbox also supports periodic background sync.

Conclusion

The Share Target API is a simple way to deeply integrate your Progressive Web App into user's devices, putting them on-par with platform-specific applications for the critical task of sharing content between apps. But doing so usually requires a server available to receive the request. By leveraging Workbox to create a share target route directly in your service worker, your app is free of this constraint, allowing Share Target to work for apps while offline and without backends.

Photo by Elaine Casap on Unsplash