Skip to content

Receive shared content (share target)

Published Updated

In one line: declaring share_target in your Web App Manifest registers your installed PWA in the OS native share sheet, so other apps can share URLs, text, titles, and files directly into your app instead of you building a separate import flow.

Per MDN, share_target requires an action (the URL that receives the share) and params (the mapping of share fields to request parameters); method and enctype are optional and default to "GET" and "application/x-www-form-urlencoded":

{
"share_target": {
"action": "/share-handler",
"method": "POST",
"enctype": "multipart/form-data",
"params": {
"title": "title",
"text": "text",
"url": "url",
"files": [
{ "name": "media", "accept": ["image/*", "video/*"] }
]
}
}
}

The default GET with no files is enough for text/URL-only sharing; receiving files requires explicitly setting "method": "POST" and "enctype": "multipart/form-data", per MDN.

2. Handle the request in your service worker

Section titled “2. Handle the request in your service worker”

Once the OS has offered your app in the share sheet and the user picks it, the browser sends a POST (or GET) to action exactly like a form submission. Intercept it in your service worker’s fetch handler and read the shared data. MDN says a POST share request is ideally answered with a 303 redirect to avoid repeated POST submissions on refresh, and Chrome for Developers demonstrates that pattern:

self.addEventListener('fetch', (event) => {
const url = new URL(event.request.url);
if (event.request.method === 'POST' && url.pathname === '/share-handler') {
event.respondWith((async () => {
const formData = await event.request.formData();
const files = formData.getAll('media');
const cache = await caches.open('shared-content');
await Promise.all(
files.map((file, i) => cache.put(`/shared-file-${i}`, new Response(file)))
);
// Redirect (with the flag the app checks for a share launch) to a page
// that reads the cached files back out.
return Response.redirect('/share-handler/view?shared=1', 303);
})());
}
});

The redirected page reads whatever the service worker stored — here, the files cached above — and shows them to the user before importing:

async function loadSharedFiles() {
const cache = await caches.open('shared-content');
const keys = await cache.keys();
const files = await Promise.all(
keys
.filter((req) => req.url.includes('/shared-file-'))
.map((req) => cache.match(req).then((res) => res.blob()))
);
return files;
}

See the compatibility data for the exact browsers, platforms, and versions that support share_target:

  • Legend
  • Yes
  • Partial
  • Flag
  • No
  • Unknown
Browser / PlatformSupportVersionsConfidenceSourceNotes
Chrome (Android)Yes76lowsource—
Edge (Desktop)Yes89lowsource1
Samsung InternetYes12.0lowsource—
Firefox (Desktop)No—lowsource2
Safari (iOS)No—lowsource3
Safari (macOS)No—lowsource4
  1. Limited file support in GET mode.
  2. No desktop install path consuming share_target.
  3. Not supported on iOS.
  4. Not supported on macOS.

Source data: /compatibility/manifest-share-target.json · Global usage: 72 % (StatCounter 2026-05)

Source: spec · MDN · Last verified 2026-07-11 · Confidence: low (computed from sources)

Detecting the redirected path and falling back

Section titled “Detecting the redirected path and falling back”

This example adds its own shared query parameter to the redirect. The page can detect that application-defined redirect path and otherwise render its normal view:

function isSharedRedirect() {
return new URLSearchParams(location.search).has('shared');
}
async function loadSharedFiles() {
if (!('caches' in window)) {
// No Cache Storage support in this context — nothing to read back.
return [];
}
const cache = await caches.open('shared-content');
const keys = await cache.keys();
return Promise.all(
keys
.filter((req) => req.url.includes('/shared-file-'))
.map((req) => cache.match(req).then((res) => res.blob()))
);
}
if (isSharedRedirect()) {
loadSharedFiles().then(renderSharedFiles);
} else {
// The app-defined redirect marker is absent — render the normal starting view.
renderDefaultView();
}
  • share_target only takes effect for an installed PWA — an unrelated tab is not offered in the OS share sheet.
  • File sharing requires "method": "POST" and "enctype": "multipart/form-data", per MDN — a GET-only target cannot receive files.
  • Prefer a 303 redirect after handling the POST; MDN calls this ideal for avoiding repeated POST submissions when the page is refreshed.
  • Give the redirected route an application-defined marker (e.g. a query parameter) so it can fall back to the normal UI when opened directly.
  • Clear or expire cached shared files after reading them so repeated shares don’t accumulate stale data.