Share Target
The Web Share Target API allows your PWA to receive content shared from other applications, making it appear alongside native apps in the system's share menu. Users can share text, links, and files directly to your PWA.
Overview
Share Target enables:
Content reception: Receive shared content from other apps
Native integration: Appear in OS share sheet
File handling: Accept images, documents, and other files
Seamless UX: Process shared content within your PWA
How It Works
User shares content: From another app, user clicks "Share"
Your PWA appears: Listed in the share menu
User selects PWA: Chooses your app as destination
PWA opens: Your app receives the shared content
Content processed: Handle the shared data in your application
Browser Support
Check support:
Chrome/Edge: Full support (Android, Desktop)
Safari: Limited support
Firefox: Not supported yet
Basic Configuration
Sharing Text and URLs
Simple configuration to receive text and links:
pwa:
manifest:
share_target:
action: "/share"
method: "GET"
params:
title: "title"
text: "text"
url: "url"How it works: When content is shared, your PWA opens at /share?title=...&text=...&url=...
Sharing Files
Accept image files via POST:
pwa:
manifest:
share_target:
action: "/share-image"
method: "POST"
enctype: "multipart/form-data"
params:
files:
- name: "image"
accept: ["image/*"]Complete Configuration
Handle text, URLs, and multiple file types:
pwa:
manifest:
share_target:
action: "/share-content"
method: "POST"
enctype: "multipart/form-data"
params:
title: "title"
text: "text"
url: "url"
files:
- name: "media"
accept:
- "image/*"
- "video/*"
- "audio/*"Configuration Parameters
action
actionType: string or URL object Required: Yes
The URL endpoint that handles shared content.
# Simple path
action: "/share"
# Route name
action:
route: "app_share"
# With parameters
action:
route: "app_share"
params:
type: "media"method
methodType: string Values: GET or POST Default: GET
HTTP method for sharing.
Use GET when:
Sharing only text/URLs (no files)
Content is read-only
No data mutation
Use POST when:
Sharing files
Creating new entries (bookmarks, posts)
Mutating application state
# GET for simple text/URL sharing
method: "GET"
# POST for file sharing
method: "POST"enctype
enctypeType: string Values: application/x-www-form-urlencoded or multipart/form-data Default: application/x-www-form-urlencoded
Encoding type for POST requests. Use multipart/form-data when accepting files.
# For file uploads (required)
enctype: "multipart/form-data"
# For text-only POST (default)
enctype: "application/x-www-form-urlencoded"params
paramsType: object
Defines parameter names for shared content.
Text Parameters
params:
title: "title" # Query param name for shared title
text: "text" # Query param name for shared text
url: "url" # Query param name for shared URLFile Parameters
params:
files:
- name: "photos" # Form field name
accept: # Accepted types
- "image/png"
- "image/jpeg"
- "image/gif"
- "image/webp"Symfony Implementation
Controller for Text/URL Sharing (GET)
<?php
namespace App\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Attribute\Route;
class ShareController extends AbstractController
{
#[Route('/share', name: 'app_share', methods: ['GET'])]
public function share(Request $request): Response
{
$title = $request->query->get('title', '');
$text = $request->query->get('text', '');
$url = $request->query->get('url', '');
// Process the shared content
// For example, pre-fill a form or create a new post
return $this->render('share/index.html.twig', [
'title' => $title,
'text' => $text,
'url' => $url,
]);
}
}Controller for File Sharing (POST)
<?php
namespace App\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\File\UploadedFile;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Attribute\Route;
class ShareController extends AbstractController
{
#[Route('/share-image', name: 'app_share_image', methods: ['POST'])]
public function shareImage(Request $request): Response
{
/** @var UploadedFile|null $image */
$image = $request->files->get('image');
if (!$image) {
return $this->redirectToRoute('app_home');
}
// Validate the file
if (!$image->isValid()) {
$this->addFlash('error', 'Invalid file upload');
return $this->redirectToRoute('app_home');
}
// Process the image (save, resize, etc.)
$filename = $this->saveSharedImage($image);
// Redirect to avoid resubmission (303 See Other)
return $this->redirectToRoute('app_image_view', [
'filename' => $filename
], Response::HTTP_SEE_OTHER);
}
private function saveSharedImage(UploadedFile $image): string
{
$filename = uniqid() . '.' . $image->guessExtension();
$image->move(
$this->getParameter('uploads_directory'),
$filename
);
return $filename;
}
}Complete Handler (Text + Files)
<?php
namespace App\Controller;
use App\Entity\Post;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Attribute\Route;
class ShareController extends AbstractController
{
#[Route('/share-content', name: 'app_share_content', methods: ['POST'])]
public function shareContent(
Request $request,
EntityManagerInterface $em
): Response {
// Get text parameters
$title = $request->request->get('title', '');
$text = $request->request->get('text', '');
$url = $request->request->get('url', '');
// Get shared files
$media = $request->files->get('media');
// Create a new post with shared content
$post = new Post();
$post->setTitle($title ?: 'Shared Content');
$post->setContent($text);
$post->setSourceUrl($url);
// Handle media files
if ($media && $media->isValid()) {
$filename = $this->handleMediaFile($media);
$post->setMediaFile($filename);
}
$em->persist($post);
$em->flush();
// Redirect with 303 See Other
return $this->redirectToRoute('app_post_view', [
'id' => $post->getId()
], Response::HTTP_SEE_OTHER);
}
private function handleMediaFile($file): string
{
// Handle image, video, or audio based on MIME type
$mimeType = $file->getMimeType();
if (str_starts_with($mimeType, 'image/')) {
return $this->handleImage($file);
} elseif (str_starts_with($mimeType, 'video/')) {
return $this->handleVideo($file);
} elseif (str_starts_with($mimeType, 'audio/')) {
return $this->handleAudio($file);
}
throw new \InvalidArgumentException('Unsupported file type');
}
}Common Use Cases
1. Social Media: Share Posts
Accept text and images for new posts:
pwa:
manifest:
share_target:
action: "/create-post"
method: "POST"
enctype: "multipart/form-data"
params:
title: "title"
text: "content"
url: "link"
files:
- name: "photo"
accept: ["image/*"]2. Bookmarking App
Save shared URLs:
pwa:
manifest:
share_target:
action: "/save-bookmark"
method: "POST"
enctype: "application/x-www-form-urlencoded"
params:
title: "title"
url: "url"
text: "notes"#[Route('/save-bookmark', name: 'app_save_bookmark', methods: ['POST'])]
public function saveBookmark(Request $request, EntityManagerInterface $em): Response
{
$bookmark = new Bookmark();
$bookmark->setTitle($request->request->get('title'));
$bookmark->setUrl($request->request->get('url'));
$bookmark->setNotes($request->request->get('notes'));
$bookmark->setCreatedAt(new \DateTime());
$em->persist($bookmark);
$em->flush();
return $this->redirectToRoute('app_bookmarks', [], Response::HTTP_SEE_OTHER);
}3. Note-Taking App
Capture shared text and links:
pwa:
manifest:
share_target:
action: "/new-note"
method: "GET"
params:
title: "title"
text: "content"
url: "source"4. Photo Gallery
Accept multiple images:
pwa:
manifest:
share_target:
action: "/upload-photos"
method: "POST"
enctype: "multipart/form-data"
params:
files:
- name: "photos[]"
accept:
- "image/jpeg"
- "image/png"
- "image/webp"
- "image/heic"5. Music Player
Share audio files:
pwa:
manifest:
share_target:
action: "/add-to-playlist"
method: "POST"
enctype: "multipart/form-data"
params:
files:
- name: "audio"
accept:
- "audio/mpeg"
- "audio/mp4"
- "audio/ogg"
- "audio/wav"Frontend Handling
Pre-fill Form with Shared Data
<form action="{{ path('app_create_post') }}" method="post">
<div class="form-group">
<label for="title">Title</label>
<input type="text"
id="title"
name="title"
class="form-control"
value="{{ title }}">
</div>
<div class="form-group">
<label for="content">Content</label>
<textarea id="content"
name="content"
class="form-control"
rows="5">{{ text }}</textarea>
</div>
{% if url %}
<div class="form-group">
<label for="source">Source URL</label>
<input type="url"
id="source"
name="source"
class="form-control"
value="{{ url }}"
readonly>
</div>
{% endif %}
<button type="submit" class="btn btn-primary">Share</button>
</form>Handle Shared Files with JavaScript
<form id="shareForm" method="post" enctype="multipart/form-data">
<div id="preview"></div>
<button type="submit">Upload</button>
</form>
<script>
document.addEventListener('DOMContentLoaded', () => {
const form = document.getElementById('shareForm');
const preview = document.getElementById('preview');
// Check if there's a shared file
const urlParams = new URLSearchParams(window.location.search);
if (urlParams.has('file')) {
// File was shared
showFilePreview();
}
form.addEventListener('submit', async (e) => {
e.preventDefault();
const formData = new FormData(form);
const response = await fetch(form.action, {
method: 'POST',
body: formData
});
if (response.ok) {
window.location.href = await response.text();
}
});
});
function showFilePreview() {
// Display preview of shared file
const preview = document.getElementById('preview');
preview.innerHTML = '<p>File received! Processing...</p>';
}
</script>File Type Handling
Accept Specific Image Formats
params:
files:
- name: "image"
accept:
- "image/png"
- "image/jpeg"
- "image/webp"Accept Documents
params:
files:
- name: "document"
accept:
- "application/pdf"
- ".doc"
- ".docx"
- "application/vnd.ms-excel"
- "text/plain"Accept Videos
params:
files:
- name: "video"
accept:
- "video/mp4"
- "video/webm"
- "video/ogg"Multiple File Types
params:
files:
- name: "media"
accept:
- "image/*"
- "video/*"
- name: "docs"
accept:
- "application/pdf"
- ".doc"
- ".docx"Testing Share Target
Manual Testing on Android
Install your PWA on Android device
Open another app (e.g., Chrome, Gallery)
Find content to share (webpage, photo)
Tap Share button
Select your PWA from share sheet
Verify content is received correctly
Testing in Chrome DevTools
Share Target can be tested using Chrome DevTools on desktop:
1. Open your PWA
2. Open DevTools (F12)
3. Go to Application → Manifest
4. Click "Share Target" section
5. Use "Test" button to simulate sharingProgrammatic Testing
Test your share endpoint directly:
# Test GET sharing (text/URL)
curl "http://localhost:8000/share?title=Test&text=Hello&url=https://example.com"
# Test POST sharing (file)
curl -X POST http://localhost:8000/share-image \
-F "[email protected]"Best Practices
1. Use HTTP 303 See Other for POST
Prevent double submissions:
// ✓ Correct - Use 303
return $this->redirectToRoute('app_success', [], Response::HTTP_SEE_OTHER);
// ✗ Wrong - Use default 302
return $this->redirectToRoute('app_success');2. Validate Shared Content
Always validate received data:
public function shareContent(Request $request): Response
{
// Validate text length
$text = $request->request->get('text', '');
if (strlen($text) > 5000) {
$this->addFlash('error', 'Text too long');
return $this->redirectToRoute('app_home');
}
// Validate URL
$url = $request->request->get('url', '');
if ($url && !filter_var($url, FILTER_VALIDATE_URL)) {
$this->addFlash('error', 'Invalid URL');
return $this->redirectToRoute('app_home');
}
// Validate file
$file = $request->files->get('image');
if ($file && !$file->isValid()) {
$this->addFlash('error', 'Invalid file');
return $this->redirectToRoute('app_home');
}
// Process valid content...
}3. Handle Missing Parameters
Content might be incomplete:
public function share(Request $request): Response
{
$title = $request->query->get('title', '');
$text = $request->query->get('text', '');
$url = $request->query->get('url', '');
// At least one parameter should be present
if (empty($title) && empty($text) && empty($url)) {
return $this->redirectToRoute('app_home');
}
// Use fallbacks
$title = $title ?: 'Shared Content';
// Process...
}4. Provide User Feedback
Show success/error messages:
public function shareImage(Request $request): Response
{
try {
// Process image
$filename = $this->saveSharedImage($request->files->get('image'));
$this->addFlash('success', 'Image shared successfully!');
return $this->redirectToRoute('app_image_view', [
'filename' => $filename
], Response::HTTP_SEE_OTHER);
} catch (\Exception $e) {
$this->addFlash('error', 'Failed to share image: ' . $e->getMessage());
return $this->redirectToRoute('app_home', [], Response::HTTP_SEE_OTHER);
}
}5. Authenticate Users
Require authentication for sharing:
#[Route('/share-content', name: 'app_share_content')]
#[IsGranted('ROLE_USER')]
public function shareContent(Request $request): Response
{
// User must be logged in to share content
// ...
}Or redirect to login:
public function shareContent(Request $request): Response
{
if (!$this->getUser()) {
// Store shared content in session
$this->get('session')->set('pending_share', [
'title' => $request->request->get('title'),
'text' => $request->request->get('text'),
'url' => $request->request->get('url'),
]);
return $this->redirectToRoute('app_login');
}
// Process share...
}Debugging
Check Manifest
Verify share_target in manifest:
1. Open your PWA
2. DevTools (F12) → Application → Manifest
3. Check "share_target" section
4. Verify action, method, and paramsLog Received Data
Debug what your app receives:
public function share(Request $request): Response
{
// Log all received data
$this->logger->info('Share received', [
'query' => $request->query->all(),
'request' => $request->request->all(),
'files' => array_keys($request->files->all()),
]);
// ...
}Test Endpoint Directly
Test without share sheet:
<!-- Create test form -->
<form action="/share-image" method="post" enctype="multipart/form-data">
<input type="file" name="image" accept="image/*">
<button type="submit">Test Share</button>
</form>Limitations
Browser Support
Android: Full support in Chrome, Edge, Samsung Internet
iOS/Safari: Not supported yet
Desktop: Limited (must install PWA first)
Firefox: Not supported
Platform Restrictions
Requires PWA to be installed
Only works on supported platforms
File size limits apply
Some MIME types may not work on all devices
Security Considerations
Validate File Types
public function shareImage(Request $request): Response
{
$image = $request->files->get('image');
// Check MIME type
$allowedTypes = ['image/jpeg', 'image/png', 'image/webp'];
if (!in_array($image->getMimeType(), $allowedTypes)) {
throw new \InvalidArgumentException('Invalid file type');
}
// Check file extension
$extension = $image->guessExtension();
if (!in_array($extension, ['jpg', 'jpeg', 'png', 'webp'])) {
throw new \InvalidArgumentException('Invalid file extension');
}
// Process...
}Limit File Size
public function shareImage(Request $request): Response
{
$image = $request->files->get('image');
// Check file size (10MB max)
if ($image->getSize() > 10 * 1024 * 1024) {
throw new \InvalidArgumentException('File too large');
}
// Process...
}Sanitize Input
use Symfony\Component\String\Slugger\SluggerInterface;
public function share(Request $request, SluggerInterface $slugger): Response
{
$title = $request->query->get('title', '');
$text = $request->query->get('text', '');
// Sanitize title
$title = strip_tags($title);
$title = $slugger->slug($title);
// Sanitize text (allow some HTML)
$text = strip_tags($text, '<p><br><strong><em>');
// Process...
}Related Documentation
Shortcuts - App shortcuts configuration
File Handlers - Handle file types
Icons - App icons for share sheet
Resources
MDN Web Share Target: https://developer.mozilla.org/en-US/docs/Web/Manifest/share_target
Web.dev Article: https://web.dev/web-share-target/
Chrome Developers: https://developer.chrome.com/docs/capabilities/web-apis/web-share-target
W3C Spec: https://w3c.github.io/web-share-target/
Last updated
Was this helpful?