CLI tool
MaxAst/expo-share-extension avatar
MaxAst/expo-share-extension

expo-share-extension adds a real iOS share sheet target to an Expo app

Expo config plugin for creating iOS share extensions with a custom view.

554 stars33 forksTypeScriptMIT

At a glance

What is it?
expo-share-extension is an Expo config plugin that creates an iOS share extension with a custom React Native view, handling the native target wiring, the activation rules and the shared data plumbing that an app otherwise has to do by hand in Xcode. It suits Expo developers who want a Pinterest style share target, and it is iOS only, with a font scaling quirk that catches people out on the first run.
Who is it for?
Expo teams that want a branded iOS share sheet target, with a custom React Native view instead of the default one, are the people this plugin is for, and the fact that it is a config plugin rather than a native module means the extension target, the app group and the activation rules are generated from your app config rather than committed Xcode project changes.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Activity is slowing. The repository last received commits 6 months ago.
What is it written in?
Mainly TypeScript, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.

Editorial analysis

A share target with your own view, not the default one

The iOS share sheet is where a user sends a photo, a link or a snippet of text into another app, and by default the target you build is a plain native screen. expo-share-extension exists to replace that with a custom view built in React Native, similar to what Pinterest or a messaging app shows, so the user lands in your interface rather than in a system form. The overview describes it as creating an iOS share extension with a custom view, and the feature list names what that buys you: support for Apple Sign-In, React Native Firebase including the shared auth session via access groups, a custom background, a custom height and custom fonts. The Apple Sign-In and shared session points matter more than they look, because a share extension is a separate process from the host app, and anything that needs an authenticated session has to get that session across the boundary through an access group.

Two entry points and a separate Metro bundle

The build of a share extension is not a single app, and the readme makes the two-entry-point structure explicit. Your index.js is the main app entry, registering the root component the usual way, or importing expo-router/entry if you use the router. The index.share.js is the share extension entry, and it registers a root component under a specific name, with a comment stating that the first argument to registerComponent must be shareExtension.

ts
import { AppRegistry } from "react-native";

// could be any component you want to use as the root component of your share extension's bundle
import ShareExtension from "./ShareExtension";

// IMPORTANT: the first argument to registerComponent, must be "shareExtension"
AppRegistry.registerComponent("shareExtension", () => ShareExtension);

Because the extension is a separate bundle, Metro has to be taught to produce it, which is what the withShareExtension wrapper in metro.config.js does, wrapping the default Expo config.

js
// Learn more https://docs.expo.io/guides/customizing-metro
const { getDefaultConfig } = require("expo/metro-config");
const { withShareExtension } = require("expo-share-extension/metro");

module.exports = withShareExtension(getDefaultConfig(__dirname), {
  // [Web-only]: Enables CSS support in Metro.
  isCSSEnabled: true,
});

If you have no metro.config.js, the readme tells you to run npx expo customize metro.config.js first. The repository carries a metro.js at the root, an app.plugin.js as the plugin entry point, a plugin directory, an ios directory, and an example directory with its own index.js, index.share.js, metro.config.js and app.json, so you can read a working configuration rather than assembling one from the prose.

Shared content arrives as a typed prop, and the type is exported

The data handoff is simple and typed. The shared data is passed to the share extension's root component as an initial prop, and the type is a plain object with optional fields for files, images, videos, text, url and preprocessingResults, and you can import InitialProps from expo-share-extension to type your root component's props. The url case is worth pausing on, because the plugin also supports the web page rule, and in that case you no longer receive url in the initial props unless you extract it in your preprocessing JavaScript file, and what you get instead is the preprocessingResults field. The preprocessingResults field is also what the Safari web page sharing path delivers. So the shape of what your component receives depends on which activation rule fired, which is a detail that is easy to get wrong and hard to debug once the view renders empty.

Activation rules are configured in your app config, not in Xcode

The plugin supports almost all of Apple's NSExtensionActivationRules, and the list it handles covers text, web URL, web page with preprocessing, image, movie and file, each with a max count. The rules go in your app.json or app.config file as an options object on the plugin entry, with a type and a max.

json
[
  "expo-share-extension",
  {
    "activationRules": [
      {
        "type": "file",
        "max": 3
      },
      {
        "type": "image",
        "max": 2
      },
      {
        "type": "video",
        "max": 1
      },
      {
        "type": "text"
      },
      {
        "type": "url",
        "max": 1
      }
    ]
  }
]

The type field can be file, image, video, text or url, and if no max is given the default is 1. If you do not specify activationRules at all, the plugin enables the url and text rules by default, for backwards compatibility. The two rules that are not yet covered are the attachments variants, with a max count and a min count, and the readme explicitly asks for contributions to support those. So the plugin can express most of what you would want in a share sheet, and the gap is narrow and named.

The font scaling quirk will be your first bug

The note at the top of the readme is the most operationally important line in the document, because the failure it describes is silent. The default Text and TextInput components from React Native do not work in the share extension due to a font scaling issue, and the fix is either to set allowFontScaling to false or to import the given components from expo-share-extension. A component that renders nothing, or renders at the wrong size, in a share extension is the kind of bug that looks like a config problem rather than a font problem, and the answer is not in the usual places. The plugin ships its own components precisely because the default ones misbehave, and a team that reads the note before the first device build avoids a day of it.

The iOS privacy manifest needs an entry for file timestamps

If you use the image and video activation rules, the readme asks you to add a privacy manifest entry to your app.json, and it is not optional housekeeping. The manifest declares the NSPrivacyAccessedAPITypes array with an NSPrivacyAccessedAPIType of NSPrivacyAccessedAPICategoryFileTimestamp and the reasons C617.1.

jsonc
{
  // ...
  "ios": {
    // ...
    "privacyManifests": {
      "NSPrivacyAccessedAPITypes": [
        {
          "NSPrivacyAccessedAPIType": "NSPrivacyAccessedAPICategoryFileTimestamp",
          "NSPrivacyAccessedAPITypeReasons": ["C617.1"],
        },
        // ...
      ],
    },
  },
}

The reason it is tied to the image and video rules is that handling those attachments involves reading file timestamps, which is a category of API access Apple requires you to declare with a justification code. A missing entry does not stop the extension from being installed, it stops the app from passing App Store validation, which is the kind of failure that arrives weeks after the code works.

Where expo-share-extension is the wrong choice

The first wrong case is an Android app, because the extension it creates is an iOS share extension and there is no Android equivalent here, so a cross-platform team ends up with a native Android share target written by hand beside it. The second is a bare React Native project, because the whole mechanism is an Expo config plugin, so without the Expo config pipeline there is no plugin to run and the native target has to be created in Xcode by hand. The third is a team that wants the attachment activation rules, because those are the two the plugin explicitly does not support yet. The fourth is a project that needs a share target to reach the host app's state, since the extension runs in its own process and anything shared has to go through the initial props, the shared auth session via access groups, or the preprocessing file, and there is no direct call into the host app from the readme's description. The fifth is a team that expects a stable release line, because the newest version in the release list is a beta, and the version that works with the current Expo SDK may be several majors behind the tip.

Editorial conclusion

Expo teams that want a branded iOS share sheet target, with a custom React Native view instead of the default one, are the people this plugin is for, and the fact that it is a config plugin rather than a native module means the extension target, the app group and the activation rules are generated from your app config rather than committed Xcode project changes. It is the wrong tool for an Android app, since the target it creates is iOS, and it is the wrong tool for a bare React Native project with no Expo config plugin pipeline. Check three things before you install it: that your Expo SDK version has a matching plugin version, since the compatibility table maps SDK 54 to 5.0.0 and above, SDK 53 to 4.0.0 and above, SDK 52 to 2.0.0 or 3.0.0 and above, SDK 51 to 1.5.3 and SDK 50 to 1.0.0, that you set allowFontScaling to false or use the components the package exports, because the default React Native Text and TextInput components do not work in a share extension due to a font scaling issue, and that your activation rules match what you intend, since the plugin enables the url and text rules by default and the remaining attachment rules are not yet supported. The licence is MIT, the newest release is a 6.0.0 beta from 2026-02-23, and the last push was on 2026-04-12.

Frequently asked questions

What does expo-share-extension do?

It is an Expo config plugin that creates an iOS share extension with a custom view, similar to what Pinterest shows, rather than the default system target. It supports Apple Sign-In, React Native Firebase including the shared auth session via access groups, a custom background, a custom height and custom fonts.

How do I install expo-share-extension?

With npx expo install expo-share-extension. You then add expo-share-extension to the plugins array in your app.json or app.config.js, make sure package.json has the correct main entry, and wrap your metro config with withShareExtension from expo-share-extension/metro.

Why do my Text components not render in the share extension?

The default Text and TextInput components from React Native do not work in the share extension due to a font scaling issue. The fix is to set allowFontScaling to false or to import the components the package provides from expo-share-extension.

How do I share the received content with my share extension view?

The shared data is passed to the root component as an initial prop, with optional files, images, videos, text, url and preprocessingResults fields, and you can import InitialProps from expo-share-extension to type it. When the web page activation rule is used you receive preprocessingResults instead of url unless you extract it in the preprocessing JavaScript file.

Which expo-share-extension version matches my Expo SDK?

SDK 54 needs 5.0.0 or later, SDK 53 needs 4.0.0 or later, SDK 52 needs 2.0.0 or 3.0.0 or later, SDK 51 needs 1.5.3 or later, and SDK 50 needs 1.0.0 or later. The plugin enables the url and text activation rules by default, and the attachment rules are not yet supported.

Official sources

  1. Issues
  2. License: MIT
  3. MaxAst/expo-share-extension on GitHub
  4. README
  5. Releases
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/maxast-expo-share-extension.svg)](https://hysenlabs.com/projects/maxast-expo-share-extension)

Was this useful?