Share Extensions allow users to share content from other apps directly to your app. They appear in the iOS share sheet and can receive text, URLs, images, and other data types.
Getting Started
Generate the share extension
This creates targets/share/ with:
expo-target.config.js — Target configuration
ShareViewController.swift — Extension entry point with SwiftUI
Configure the target
targets/share/expo-target.config.js
/** @type {import('@bacons/apple-targets/app.plugin').Config} */
module.exports = {
type: "share",
icon: "../../assets/icon.png",
// Share app groups to communicate with main app
entitlements: {
"com.apple.security.application-groups": ["group.com.yourapp.shared"],
},
};
Run prebuild
npx expo prebuild -p ios --clean
Test in Xcode
Select the share extension scheme and run it. Xcode will prompt you to choose an app to run the extension within (e.g., Safari).
Share Extension Implementation
The generated template uses SwiftUI for a modern UI:
targets/share/ShareViewController.swift
import UIKit
import Social
import SwiftUI
class ShareViewController: SLComposeServiceViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Extract the shared text from the extension context
if let item = extensionContext?.inputItems.first as? NSExtensionItem,
let itemProvider = item.attachments?.first as? NSItemProvider,
itemProvider.hasItemConformingToTypeIdentifier("public.plain-text") {
itemProvider.loadItem(forTypeIdentifier: "public.plain-text", options: nil) { [weak self] (string, error) in
if let error = error {
print("ItemProvider error: \(error)")
}
if let string = string as? String {
DispatchQueue.main.async {
self?.sharedText = string
self?.setupUI()
}
}
}
} else {
setupUI()
}
}
private var sharedText: String = "Loading..."
func setupUI() {
let contentView = ShareView(sharedText: sharedText)
let hostingController = UIHostingController(rootView: contentView)
addChild(hostingController)
hostingController.view.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(hostingController.view)
NSLayoutConstraint.activate([
hostingController.view.leadingAnchor.constraint(equalTo: view.leadingAnchor),
hostingController.view.trailingAnchor.constraint(equalTo: view.trailingAnchor),
hostingController.view.topAnchor.constraint(equalTo: view.topAnchor),
hostingController.view.bottomAnchor.constraint(equalTo: view.bottomAnchor)
])
hostingController.didMove(toParent: self)
}
override func isContentValid() -> Bool {
// Validate content here
return true
}
override func didSelectPost() {
// Process the shared content
let finalText = "Here is a nice text I found: " + contentText
// Return the result
let outputItem = NSExtensionItem()
outputItem.attributedContentText = NSAttributedString(string: finalText)
extensionContext?.completeRequest(returningItems: [outputItem], completionHandler: nil)
}
}
struct ShareView: View {
let sharedText: String
var body: some View {
Text("Selected: \(sharedText)")
.font(.title)
.foregroundColor(.blue)
.padding()
}
}
Handling Different Content Types
Share extensions can receive various content types. Here’s how to handle them:
Text Content
if itemProvider.hasItemConformingToTypeIdentifier("public.plain-text") {
itemProvider.loadItem(forTypeIdentifier: "public.plain-text", options: nil) { (item, error) in
if let text = item as? String {
// Handle text
}
}
}
URLs
if itemProvider.hasItemConformingToTypeIdentifier("public.url") {
itemProvider.loadItem(forTypeIdentifier: "public.url", options: nil) { (item, error) in
if let url = item as? URL {
// Handle URL
}
}
}
Images
if itemProvider.hasItemConformingToTypeIdentifier("public.image") {
itemProvider.loadItem(forTypeIdentifier: "public.image", options: nil) { (item, error) in
if let image = item as? UIImage {
// Handle image
}
}
}
Communicating with Your Main App
Use App Groups to share data between the share extension and your main app:
Configure App Groups
Add to both your main app’s app.json and the share extension’s expo-target.config.js:{
"expo": {
"ios": {
"entitlements": {
"com.apple.security.application-groups": ["group.com.yourapp.shared"]
}
}
}
}
targets/share/expo-target.config.js
module.exports = (config) => ({
type: "share",
entitlements: {
"com.apple.security.application-groups":
config.ios.entitlements["com.apple.security.application-groups"],
},
});
Write data from the extension
let defaults = UserDefaults(suiteName: "group.com.yourapp.shared")
defaults?.set(sharedText, forKey: "lastSharedText")
defaults?.synchronize()
Read data in your React Native app
import { ExtensionStorage } from "@bacons/apple-targets";
const storage = new ExtensionStorage("group.com.yourapp.shared");
const lastSharedText = storage.get("lastSharedText");
See the Sharing Data guide for more details.
Using React Native in Share Extensions
Running React Native in share extensions is experimental and increases app size significantly.
If you need React Native:
- Set
exportJs: true in your config:
module.exports = {
type: "share",
exportJs: true,
};
-
Create a
targets/share/pods.rb file with React Native dependencies (similar to App Clips)
-
Use
expo-application to detect the share extension:
import * as Application from 'expo-application';
const isShareExtension = Application.applicationId?.includes('.share');
Customizing the Info.plist
Configure which apps can share to your extension by editing targets/share/Info.plist:
<key>NSExtension</key>
<dict>
<key>NSExtensionAttributes</key>
<dict>
<key>NSExtensionActivationRule</key>
<dict>
<!-- Accept text content -->
<key>NSExtensionActivationSupportsText</key>
<true/>
<!-- Accept URLs -->
<key>NSExtensionActivationSupportsWebURLWithMaxCount</key>
<integer>1</integer>
<!-- Accept images -->
<key>NSExtensionActivationSupportsImageWithMaxCount</key>
<integer>5</integer>
</dict>
</dict>
<key>NSExtensionPointIdentifier</key>
<string>com.apple.share-services</string>
<key>NSExtensionPrincipalClass</key>
<string>ShareViewController</string>
</dict>
Opening Your Main App
To open your main app after sharing:
override func didSelectPost() {
// Save data first
let defaults = UserDefaults(suiteName: "group.com.yourapp.shared")
defaults?.set(contentText, forKey: "sharedContent")
// Open the main app with a URL
let url = URL(string: "myapp://shared")!
extensionContext?.open(url, completionHandler: { success in
self.extensionContext?.completeRequest(returningItems: [], completionHandler: nil)
})
}
Handle the URL in your Expo app:
import { useURL } from 'expo-linking';
import { useEffect } from 'react';
import { ExtensionStorage } from '@bacons/apple-targets';
export default function App() {
const url = useURL();
useEffect(() => {
if (url?.includes('shared')) {
const storage = new ExtensionStorage('group.com.yourapp.shared');
const content = storage.get('sharedContent');
// Handle the shared content
}
}, [url]);
return <YourApp />;
}
Tips
Keep it fast: Share extensions should launch quickly. Avoid loading large dependencies.
Validate input: Always check that the content type matches what you expect.
Handle errors gracefully: Users can share from any app, so expect unexpected data.
Next Steps