Using @capgo/capacitor-share-target Package
The @capgo/capacitor-share-target package enables your Capacitor app to receive shared content from other applications. This makes your app appear in the system share sheet, allowing users to share text, URLs, images, videos, and files directly to your app. This tutorial will guide you through installation, configuration, and practical implementation.
Installation
Install the package using your preferred package manager:
npm install @capgo/capacitor-share-target
npx cap sync
Platform Requirements
Android
- Android 5.0 (API 21) or higher
- Intent filters configured in AndroidManifest.xml
iOS
- iOS 12.0 or higher
- Share Extension target created in Xcode
Web
Not supported - requires native platform integration.
Android Configuration
To make your app appear in Android's share menu, you need to add intent filters to your AndroidManifest.xml file inside the main <activity> tag.
Basic Text Sharing
<intent-filter>
<action android:name="android.intent.action.SEND" />
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="text/plain" />
</intent-filter>
Image Sharing
<!-- Single image -->
<intent-filter>
<action android:name="android.intent.action.SEND" />
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="image/*" />
</intent-filter>
<!-- Multiple images -->
<intent-filter>
<action android:name="android.intent.action.SEND_MULTIPLE" />
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="image/*" />
</intent-filter>
Video Sharing
<intent-filter>
<action android:name="android.intent.action.SEND" />
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="video/*" />
</intent-filter>
All Content Types
<!-- Accept any type of content -->
<intent-filter>
<action android:name="android.intent.action.SEND" />
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="*/*" />
</intent-filter>
<!-- Accept multiple files of any type -->
<intent-filter>
<action android:name="android.intent.action.SEND_MULTIPLE" />
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="*/*" />
</intent-filter>
Complete AndroidManifest.xml Example
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<application>
<activity
android:name=".MainActivity"
android:exported="true">
<!-- Main launcher intent -->
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
<!-- Share target intents -->
<intent-filter>
<action android:name="android.intent.action.SEND" />
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="text/*" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.SEND" />
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="image/*" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.SEND_MULTIPLE" />
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="*/*" />
</intent-filter>
</activity>
</application>
</manifest>
iOS Configuration
iOS requires creating a Share Extension to receive shared content. This is more complex than Android but provides a native iOS sharing experience.
Step 1: Create Share Extension in Xcode
- Open your iOS project in Xcode
- Go to File > New > Target
- Select Share Extension template
- Click Next
- Name it (e.g., "ShareExtension")
- Choose Swift as the language
- Click Finish
- When prompted, Activate the scheme
Step 2: Configure App Groups
Both your main app and Share Extension need to communicate via a shared container.
- Select your main app target
- Go to Signing & Capabilities
- Click + Capability
- Add App Groups
- Click + to create a new group:
group.com.yourcompany.yourapp - Repeat steps 1-5 for your Share Extension target
Step 3: Update Share Extension Code
Replace the content of ShareViewController.swift in your Share Extension:
import UIKit
import Social
import MobileCoreServices
class ShareViewController: UIViewController {
private let appGroupIdentifier = "group.com.yourcompany.yourapp"
override func viewDidLoad() {
super.viewDidLoad()
handleSharedContent()
}
private func handleSharedContent() {
guard let extensionItem = extensionContext?.inputItems.first as? NSExtensionItem else {
closeExtension()
return
}
var sharedData: [String: Any] = [:]
if let attachments = extensionItem.attachments {
processAttachments(attachments) { data in
sharedData = data
self.saveToUserDefaults(data: sharedData)
self.closeExtension()
}
} else {
closeExtension()
}
}
private func processAttachments(_ attachments: [NSItemProvider], completion: @escaping ([String: Any]) -> Void) {
var result: [String: Any] = [:]
var processedCount = 0
for attachment in attachments {
if attachment.hasItemConformingToTypeIdentifier(kUTTypeURL as String) {
attachment.loadItem(forTypeIdentifier: kUTTypeURL as String, options: nil) { (data, error) in
if let url = data as? URL {
result["url"] = url.absoluteString
}
processedCount += 1
if processedCount == attachments.count {
completion(result)
}
}
} else if attachment.hasItemConformingToTypeIdentifier(kUTTypeText as String) {
attachment.loadItem(forTypeIdentifier: kUTTypeText as String, options: nil) { (data, error) in
if let text = data as? String {
result["text"] = text
}
processedCount += 1
if processedCount == attachments.count {
completion(result)
}
}
} else if attachment.hasItemConformingToTypeIdentifier(kUTTypeImage as String) {
attachment.loadItem(forTypeIdentifier: kUTTypeImage as String, options: nil) { (data, error) in
// Handle image
processedCount += 1
if processedCount == attachments.count {
completion(result)
}
}
}
}
}
private func saveToUserDefaults(data: [String: Any]) {
if let userDefaults = UserDefaults(suiteName: appGroupIdentifier) {
userDefaults.set(data, forKey: "SharedData")
userDefaults.synchronize()
}
}
private func closeExtension() {
extensionContext?.completeRequest(returningItems: [], completionHandler: nil)
}
}
Step 4: Update Info.plist for Share Extension
In your Share Extension's Info.plist, configure accepted content types:
<key>NSExtension</key>
<dict>
<key>NSExtensionAttributes</key>
<dict>
<key>NSExtensionActivationRule</key>
<dict>
<key>NSExtensionActivationSupportsImageWithMaxCount</key>
<integer>10</integer>
<key>NSExtensionActivationSupportsMovieWithMaxCount</key>
<integer>10</integer>
<key>NSExtensionActivationSupportsText</key>
<true/>
<key>NSExtensionActivationSupportsWebURLWithMaxCount</key>
<integer>1</integer>
</dict>
</dict>
<key>NSExtensionMainStoryboard</key>
<string>MainInterface</string>
<key>NSExtensionPointIdentifier</key>
<string>com.apple.share-services</string>
</dict>
Basic Usage
Import the Plugin
import { CapacitorShareTarget } from '@capgo/capacitor-share-target';
Listen for Shared Content
function setupShareListener() {
CapacitorShareTarget.addListener('shareReceived', (event) => {
console.log('=== Shared Content Received ===');
if (event.title) {
console.log('Title:', event.title);
}
if (event.texts && event.texts.length > 0) {
console.log('Texts:', event.texts);
}
if (event.files && event.files.length > 0) {
console.log('Files:', event.files);
}
});
}
// Initialize on app start
setupShareListener();
Complete Share Target Service
Here's a production-ready service for handling shared content:
import { CapacitorShareTarget } from '@capgo/capacitor-share-target';
export interface SharedFile {
name: string;
mimeType: string;
uri: string;
size?: number;
}
export interface SharedContent {
title?: string;
texts?: string[];
files?: SharedFile[];
timestamp: number;
}
export class ShareTargetService {
private listener: any;
private shareHandlers: Map<string, (content: SharedContent) => void> = new Map();
initialize() {
this.listener = CapacitorShareTarget.addListener('shareReceived', (event) => {
this.handleShareReceived(event);
});
console.log('ShareTarget service initialized');
}
private handleShareReceived(event: any) {
const content: SharedContent = {
title: event.title,
texts: event.texts,
files: event.files,
timestamp: Date.now()
};
console.log('Share received:', content);
// Notify all registered handlers
this.shareHandlers.forEach((handler) => {
handler(content);
});
// Process based on content type
if (content.texts && content.texts.length > 0) {
this.handleSharedText(content.texts);
}
if (content.files && content.files.length > 0) {
this.handleSharedFiles(content.files);
}
}
private handleSharedText(texts: string[]) {
texts.forEach((text) => {
if (this.isURL(text)) {
console.log('URL shared:', text);
this.handleURL(text);
} else {
console.log('Text shared:', text);
this.handlePlainText(text);
}
});
}
private handleSharedFiles(files: SharedFile[]) {
files.forEach((file) => {
console.log('File shared:', file.name, file.mimeType);
const fileType = file.mimeType.split('/')[0];
switch (fileType) {
case 'image':
this.handleImage(file);
break;
case 'video':
this.handleVideo(file);
break;
case 'audio':
this.handleAudio(file);
break;
default:
this.handleGenericFile(file);
}
});
}
private isURL(text: string): boolean {
try {
new URL(text);
return true;
} catch {
return false;
}
}
private handleURL(url: string) {
console.log('Processing URL:', url);
// Implementation: Save bookmark, open in app, etc.
}
private handlePlainText(text: string) {
console.log('Processing text:', text.substring(0, 50));
// Implementation: Create note, save to clipboard, etc.
}
private handleImage(file: SharedFile) {
console.log('Processing image:', file.name);
// Implementation: Save to gallery, upload, etc.
}
private handleVideo(file: SharedFile) {
console.log('Processing video:', file.name);
// Implementation: Save to library, process, etc.
}
private handleAudio(file: SharedFile) {
console.log('Processing audio:', file.name);
// Implementation: Add to playlist, transcribe, etc.
}
private handleGenericFile(file: SharedFile) {
console.log('Processing file:', file.name);
// Implementation: Save to documents, upload, etc.
}
registerShareHandler(key: string, handler: (content: SharedContent) => void) {
this.shareHandlers.set(key, handler);
}
unregisterShareHandler(key: string) {
this.shareHandlers.delete(key);
}
cleanup() {
if (this.listener) {
this.listener.remove();
}
this.shareHandlers.clear();
}
}
Application Examples
Bookmark Manager App
class BookmarkApp {
private shareService: ShareTargetService;
constructor() {
this.shareService = new ShareTargetService();
}
initialize() {
this.shareService.initialize();
this.shareService.registerShareHandler('bookmarks', (content) => {
if (content.texts) {
content.texts.forEach(text => {
if (this.isURL(text)) {
this.saveBookmark(text, content.title);
}
});
}
});
}
private isURL(text: string): boolean {
return text.startsWith('http://') || text.startsWith('https://');
}
private async saveBookmark(url: string, title?: string) {
console.log('Saving bookmark:', url);
const bookmark = {
url: url,
title: title || await this.fetchPageTitle(url),
timestamp: Date.now()
};
// Save to database
await this.saveToDatabase(bookmark);
// Show notification
this.showNotification(`Bookmark saved: ${bookmark.title}`);
}
private async fetchPageTitle(url: string): Promise<string> {
try {
const response = await fetch(url);
const html = await response.text();
const match = html.match(/<title>(.*?)<\/title>/i);
return match ? match[1] : url;
} catch {
return url;
}
}
private async saveToDatabase(bookmark: any) {
// Database implementation
console.log('Saved:', bookmark);
}
private showNotification(message: string) {
console.log('Notification:', message);
// Show toast or notification
}
}
// Usage
const app = new BookmarkApp();
app.initialize();
Photo Gallery App
class PhotoGalleryApp {
private shareService: ShareTargetService;
constructor() {
this.shareService = new ShareTargetService();
}
initialize() {
this.shareService.initialize();
this.shareService.registerShareHandler('gallery', async (content) => {
if (content.files) {
const images = content.files.filter(f =>
f.mimeType.startsWith('image/')
);
if (images.length > 0) {
await this.importImages(images);
}
}
});
}
private async importImages(images: SharedFile[]) {
console.log(`Importing ${images.length} image(s)`);
for (const image of images) {
try {
await this.processImage(image);
} catch (error) {
console.error('Failed to process image:', error);
}
}
this.showNotification(`Imported ${images.length} image(s)`);
}
private async processImage(image: SharedFile) {
// Read file
const response = await fetch(image.uri);
const blob = await response.blob();
// Generate thumbnail
const thumbnail = await this.generateThumbnail(blob);
// Save to gallery
await this.saveToGallery({
original: blob,
thumbnail: thumbnail,
name: image.name,
mimeType: image.mimeType,
timestamp: Date.now()
});
console.log('Image processed:', image.name);
}
private async generateThumbnail(blob: Blob): Promise<Blob> {
// Thumbnail generation logic
return blob; // Simplified
}
private async saveToGallery(imageData: any) {
// Save implementation
console.log('Saved to gallery:', imageData.name);
}
private showNotification(message: string) {
console.log('Notification:', message);
}
}
// Usage
const galleryApp = new PhotoGalleryApp();
galleryApp.initialize();
Note-Taking App
class NotesApp {
private shareService: ShareTargetService;
constructor() {
this.shareService = new ShareTargetService();
}
initialize() {
this.shareService.initialize();
this.shareService.registerShareHandler('notes', async (content) => {
await this.createNoteFromShare(content);
});
}
private async createNoteFromShare(content: SharedContent) {
let noteContent = '';
let attachments: SharedFile[] = [];
// Add title
if (content.title) {
noteContent += `# ${content.title}\n\n`;
}
// Add text content
if (content.texts && content.texts.length > 0) {
noteContent += content.texts.join('\n\n');
}
// Add file attachments
if (content.files && content.files.length > 0) {
attachments = content.files;
noteContent += '\n\n## Attachments\n';
content.files.forEach(file => {
noteContent += `- ${file.name}\n`;
});
}
// Create note
const note = {
title: content.title || 'Shared Note',
content: noteContent,
attachments: attachments,
createdAt: new Date().toISOString(),
tags: ['shared']
};
await this.saveNote(note);
this.showNotification('Note created from shared content');
}
private async saveNote(note: any) {
console.log('Note saved:', note.title);
// Database implementation
}
private showNotification(message: string) {
console.log('Notification:', message);
}
}
// Usage
const notesApp = new NotesApp();
notesApp.initialize();
Framework Integration
React Integration
import { useEffect } from 'react';
import { ShareTargetService, SharedContent } from './ShareTargetService';
function useShareTarget(onShareReceived: (content: SharedContent) => void) {
useEffect(() => {
const service = new ShareTargetService();
service.initialize();
service.registerShareHandler('react-app', onShareReceived);
return () => {
service.cleanup();
};
}, [onShareReceived]);
}
// Usage in component
function App() {
useShareTarget((content) => {
console.log('Share received in React:', content);
// Handle shared content
});
return <div>My App</div>;
}
Vue 3 Integration
import { onMounted, onUnmounted } from 'vue';
import { ShareTargetService, SharedContent } from './ShareTargetService';
export function useShareTarget(onShareReceived: (content: SharedContent) => void) {
let service: ShareTargetService;
onMounted(() => {
service = new ShareTargetService();
service.initialize();
service.registerShareHandler('vue-app', onShareReceived);
});
onUnmounted(() => {
if (service) {
service.cleanup();
}
});
}
// Usage in component
export default {
setup() {
useShareTarget((content) => {
console.log('Share received in Vue:', content);
// Handle shared content
});
}
};
Best Practices
- Initialize Early: Set up share listener as soon as your app starts
- Validate Content: Always check content types before processing
- Handle Errors: File URIs might be invalid or inaccessible
- Provide Feedback: Show users what was received
- Test Thoroughly: Test with different apps and content types
- Respect Privacy: Don't access shared content without user action
- Clean Up: Remove listeners when not needed
Troubleshooting
Android: App Not Appearing in Share Menu
- Verify intent filters are in the correct
<activity>tag - Ensure
android:exported="true"is set - Check MIME type matches what you're sharing
- Reinstall the app after manifest changes
iOS: Share Extension Not Working
- Verify App Group ID is identical in both targets
- Check that Share Extension is activated
- Ensure Info.plist has correct activation rules
- Test on a real device (simulators may have limitations)
Files Not Accessible
async function safelyAccessFile(file: SharedFile) {
try {
const response = await fetch(file.uri);
if (!response.ok) {
throw new Error('File not accessible');
}
const blob = await response.blob();
return blob;
} catch (error) {
console.error('Failed to access file:', error);
alert('Unable to access shared file');
return null;
}
}
Conclusion
The @capgo/capacitor-share-target plugin provides powerful share target capabilities for your Capacitor app. By properly configuring platform-specific settings and implementing event listeners, you can create seamless sharing experiences that integrate naturally with the device's native sharing functionality.
For more information, visit the official documentation or check the GitHub repository.