Ejecting to Expo’s Bare Workflow – IOS

I loved learning React Native through Expo. Expo’s managed workflow is so simple and elegant; it makes it easy for any beginner to jump into app development.

Why would I eject from Expo’s managed workflow?

When building my first app in Expo, I was unable to continue making progress with the managed workflow. I needed to build in-app purchases. An in-app purchase library exists for Expo, but you need to eject to the bare workflow to use it.

I tried digging through the documentation, trying to figure out a nice way to get this to work without ejecting, but came up empty handed. I figured, “I’ve been sheltered by Expo for too long, let’s see what React Native is like without it.”

And thus began my journey ejecting into the bare workflow.

Step 1: Run the Eject Script

The first step in ejecting is to run the eject script. This is as easy as running expo eject.

When you run this, you’ll see the following options:

? How would you like to eject your app?
  Read more: https://docs.expo.io/versions/latest/expokit/eject/ (Use arrow keys)
❯ Bare: I'd like a bare React Native project.
  ExpoKit: I'll create or log in with an Expo account to use React Native and the Expo SDK.
  Cancel: I'll continue with my current project structure.

You’ll want to select the default option, bare.

After this a bunch of stuff will run to setup your ios and android projects and your app startup code for each.

This is your friendly reminder to commit after each step when doing a complex migration like this one.

Install Cocoapods and Dependencies

Cocoapods is a tool you probably didn’t need while you were still working with Expo’s managed workflow. It’s used to install Swift and Objective-C dependencies on IOS. If you’re not building for iOS, you won’t need to do this part.

Cocoapods is super easy to install.

sudo gem install cocoapods

After this, you can cd into your ios folder and run pod install to install all of your dependencies for IOS.

Remove references to the Expo package

One of the tradeoffs we make when switching to Expo’s bare workflow is not being able to use some of the modules imported directly from Expo. Search for anything being imported from the Expo package like import { MyPackage } from 'expo'.

Some of the problem modules for me were:

  • Notifications
  • AppLoading
  • SplashScreen

These can be replaced fairly easily. However, Notifications can be one of the harder modules to migrate away from. I replaced my old notifications implementation with react-native-push-notifications-ios and wrote my own wrapper for the package using React Hooks.

Reconfigure broken modules

At this point, you should try running your app. To do this, open your IOS project in Xcode. Click the build button at the top. This should run the metro bundler and launch your simulator, or run on your connected device.

Please note that you won’t be able to use the Expo app to run your app anymore.

When you run your new app for the first time, I’m guessing you’re going to start hitting a lot of errors. You’ll have to look at which modules are causing errors for you, and determine if you need to reinstall the module or migrate to a new one.


One module that I had to re-install when I migrated to Expo’s bare workflow was expo-facebook. The proper pod was not installed when I ejected, so I followed these bare workflow installation instructions.

Basically, I just had to install react-native-unimodules. Then, I could expo install expo-facebook, and do a pod install to get it working again.


I also started receiving null from expoConstants.default.manifest. This caused a few issues in some more modules I was using. To side-step this issue, I added the following code to my App.js:

import Constants from 'expo-constants';
import * as manifest from './app.json';
if (!Constants.manifest){
    Constants.manifest = manifest.expo

Of course, make sure expo-constants is installed first.


I also had to refactor how I was initializing expo-sentry in my app. Previously, I was just calling it when my App.js function was being loaded. Moving my Sentry.Init call to the constructor of my main App component resolved this.

Ideally, I think it’d be best to do this type of work in a componentDidMount, since it’s likely hitting some API during initialization, but I don’t want to wait until my component has mounted to start capturing errors.

What problems did you have?

This was my experience ejecting my app from Expo’s managed workflow into Expo’s bare workflow. If you had different problems or have questions about the process, leave a comment below.