Linking in managed expo since SDK36

Newbie here, please be gentle!

Since upgrading to SDK36 and RN61, I seem unable to use some libraries. Like /react-native-device-info or the latest stack naviagor

The expo client in my IoS Simulator tells me to react-native link if <= 0.59 or use cocoaPods in my ios folder.

In managed expo, I don’t have an ios folder. I suppose that sort of thing is taken care of on the build server when I run expo build:ios

I must have a blind spot, because for a week now I have found only tips on how to do this in xCode projects, or in a bare or ExpoKit set up. They say things like this:

cd ios
pod install
cd .

e.g. Redirecting to

Is managed expo with SDK36 now restricted to libraries that don’t require linking?

Hi, and welcome!

You’ve never been able to link libraries in the managed workflow, are you sure this was working prior to SDK 36? For information on the device, we added in the Device module, which you can use in the managed workflow instead of react-native-device-info

createStackNavigator should work in the managed workflow, so can you share a repro for this issue?

1 Like


What might be confusing is that although in Expo’s managed workflow you cannot use random modules that require linking, Expo already includes a bunch of modules that would require linking normally. I mean they are already linked in Expo, so there’s no need for you to link them.

For react-navigation-stack I had problems when trying to upgrade to version 2.x, but version 1.10.3 works for me and definitely doesn’t require linking. So I believe a 1.x version of the native part of react-navigation-stack is linked with Expo’s SDK 36.

Thanks for responding, Charlie and Wodin. Very kind of you to help.

I recall (never a reliable thing) often getting messages that I needed to link, back in SDK 34, and linking seemed to fix it. So, I am surprised to learn that I have never been able to link in the managed workflow. But I trust your better knowledge!


I had trouble with createStackNavigator when I upgraded to SDK36. There seems to be two versions on Redirecting to with no explanation as to why there are two, or how to choose between them. By trial and error and a lot of rolling back with git, I managed to get things working again using the one with (1.x) at the end of the title, based on that version not mentioning CocoaPods in the docs. Is that what you would expect?

Using non-Expo modules

What I think I have learned from your comments is that I can sneak in ‘random’ modules (as Wodin calls them) in the managed Expo workflow, unless they require linking via CocoaPods. Is that why some tutorials recommend ‘expo install library-x’ rather than ’npm install library-x’ – to get the right Expo-friendly version or the one for which the Expo team has sorted out the links for us?

I successfully use non-Expo modules libraries like moment, redux, redux-persist, react-native-elements and others. If these libraries start requiring linking in a future release, which seems to be the current trend, am I stuffed?

Getting the App Store version number

Charlie, I didn’t try react-native-device-info before SDK36. I tried it the other day because neither the Expo device module nor Constants seem to help me display the App store version, just the build number. Users are getting confused when I say “make sure the app slide out panel says you are on version 1.0.78 (the build number) when the app store is displaying the Apps store version 1.0.4. So I need to find a library that doesn’t require linking.

I think part of my problem is that I don’t really understand what linking is about in this context. I suspect it’s about slimming down libraries to only use the bits they really need to include. I am very experienced in JavaScript but rather naïve about libraries and linking between them for app development. It seems that if the trend for CocaoPods continues and managed Expo doesn’t facilitate linking, its usefulness will dim. Shame because I love being able to type in ‘expo build:ios’ much more that dealing with jQuery Mobile and Cordova/PhoneGap which I have migrated the app from! I hope I have misunderstood.


I tried upgrading an app to StackNavigator 2.x and got errors, so reverted to 1.10.3 and that works for me. The StackNavigator requires some native code (which is why the documentation talks about linking) and Expo has clearly (based on the evidence. I haven’t checked the source code.) linked a version of that native code that is compatible with 1.x rather than 2.x of the StackNavigator, so this is expected, although it would be nice if expo install knew about which version of StackNavigator should be installed :slight_smile:

Basically yes, although I’m not sure I’d call it ‘sneaking in’ :slight_smile:
Any pure JavaScript code that works with e.g. React Native 0.61 will (barring some or other bug maybe) work with Expo SDK 36, and possibly other SDK versions.
Even (pure JavaScript) code that is not officially compatible with React Native can work, but this depends on whether that code depends on the DOM or NodeJS APIs that are not available in React Native/Expo.

e.g. here’s a barcode generator based on JsBarcode which does not support React Native, but I was able to get it to work by copying and modifying some code from their SVG rendering module:

I did not spend very long on the above, so I’m sure it could be improved and maybe the JsBarcode project would be willing to incorporate something like it so that it can officially support React Native. The point is pure JavaScript code does not require linking and even if it doesn’t say it works with React Native there’s still a chance it will. And if it works with React Native then it should work with a corresponding version of the Expo SDK.

Something like that, yes. expo install knows about certain version ranges of certain libraries that are compatible with certain Expo SDK versions and can install the right version of the library (using npm or yarn under the covers), but unfortunately this doesn’t cover every single library where later versions might be incompatible with Expo. If you call expo install on a library that the Expo team has not told expo install about it will just call npm install library-x or yarn add library-x and hope for the best (as far as I can tell).

The reason expo install restricts the ranges that can be installed with a particular SDK is often to do with what version of a native library has been linked into Expo, but I don’t think this is necessarily the case. i.e. A newer version of a pure JavaScript library might not be compatible with other JavaScript code included in Expo (I’m guessing) and therefore the Expo team might update expo install not to install the later versions of that library.

You would need to eject in this case.

I don’t think this is the case.

Especially for things like moment and redux that work in the browser too, there’s no point adding native code. That would just break the browser support. The only reason native code would be added (requiring linking) is if something can’t be done or can’t be done efficiently in pure JavaScript. For stuff that is already working in pure JavaScript I’m not sure it’s likely that the library will be modified to add native code, which, as I said above, would suddenly make it impossible to use in a browser.

EDIT: About your last topic:

I’m not quite clear on which version number is used for what or shows up where. (Even after reading the docs. :sweat_smile:) I have not yet published anything on the App Store, so I can’t check this myself either. But it seems that you can set the app version using expo.version and/or expo.ios.buildNumber and then access them using Constants.nativeAppVersion and/or Constants.nativeBuildVersion.

If that’s not enough, I can think of a workaround. You can stash anything you like in app.json under expo.extra and then access it with Constants.manifest.extra. So you could potentially store the App Store version number there. It seems unlikely that you’d need to do this, but it’s there if you need it. I’ve just noticed that things under Constants.manifest are updated OTA (as opposed to the other stuff from app.json).

The documentation for Constants.platform.ios.buildNumber says This may differ from the value in Constants.manifest.ios.buildNumber because the manifest can be updated over the air, whereas this value will never change for a given native binary. So maybe this is what you’re looking for?

If I were you I’d put stuff in all the different version fields in app.json such that you can distinguish between them, then put something in the app that can show all of the following:


Build the app and get it on the App Store. Check what is displayed for all of these. Then update the version numbers, publish an OTA update and check all the versions displayed in the app again. I think that would make it clear what is going on. I’m sure @charliecruzan could clarify all of this without you having to go to all this trouble, though :sweat_smile:

React Native obviously includes native code and JavaScript code. Any third party libraries/modules that are compatible with React Native might be pure JavaScript, in which case they do not need to be linked, or they might be partly native code and partly JavaScript where the JavaScript depends on the native code. If this is the case the native part needs to be built in to the native part of the React Native app when it is built. This is what linking does. It sets up the project to compile all the built-in and third party native code together. So any React Native compatible library that is pure JavaScript does not need to be linked (because there’s no native code to link/compile).

It’s possible to compile the native code portion of a third party library/module into the React Native app and then just not use the JavaScript part of it in the app. This is basically what Expo does. It links the native parts of various third party modules into the native part of Expo so that if you want to you can later include the JavaScript code that relies on these native parts if you want to. Even if you build the app and have it on the App Store without using e.g. the barcode scanner you can still enable the barcode scanner in an OTA update, because the native code that is needed is already built in to any managed Expo app.

I hope I’ve convinced you that your concerns are exaggerated :slight_smile:

Michael, you write beautifully and clearly. Are you sure you are in the right career? :smile:

Thanks for taking the time to answer my long set of questions so fully. The explanations are very helpful, especially about link. It may be that some of the guidance I have read has been for Bare or ExpoKit rather than Managed. To a beginner who began with sales pitches on the expo website, the differences are not so clear.

I have not done OTA updates since my Rwanda days so will look them up in Expo. During the pilot I have been releasing another app version through the store a couple of times a day, which is annoying for all concerned. OTA should stop that I think. I web developer mindset (OTA just happens when you reload the page) doesn’t always help with mobile apps!

I think my stack navigator hell began when I jumped from SDK34 to 36 (D’oh!) and the stack navigator stopped showing the screen content. I uninstalled all navigation modules and used expo install but I am not sure expo install always picked the right one. But I have no evidence and it was part of a four-day crisis of trying to get it working, installing Cocoa, trying to go back to SDK34 then giving up and restoring my package.json bit-by-bit with different nodule versions to the expo recommended ones if necessary.

You make a good point about the need to support in-browser use restricting my perceived trend towards CocoaPods. I must have just been unlucky with the ones I tried recently, and of course the latest Stack Navigator. Presumably, Expo will include the required native bits if they decide the latest version is worth it?

I have been putting up a few versions a day in Apple TestFlight, each with a different build number, then picking a good one to release in the App Store, at which point the App version number changes. I suppose I could increment a hard-coded value in the app that says what I expect the App Store Release number to be, but that seems a bit tacky. I will look on the forum for guidance about Expo and OTA app updates (not available till SDK37? The elephant in the room). I’ll post any questions in a more relevant topic.

Well I do think sometimes of becoming a carrot farmer :thinking:

Yes, there’s a lot to take in and it can definitely get confusing.

I might be missing your Rwanda reference :sweat_smile: but definitely look into OTA updates. They allow you to update your app without having to go through an App Store review every time. The rule of thumb is if you need to upgrade the SDK version or change something in app.json then you generally have to build a new app, but if you just make changes to the JavaScript or assets you can run expo publish and your users will automatically get the new version. (That’s the basics, but there are more details.)

Yes, in the case of StackNavigator in particular I believe that would have installed the wrong version. I should probably look into where the version ranges for expo install are located and submit a pull request to fix this or at least create an issue on Issues · expo/expo-cli · GitHub.

Yes, as far as I understand it they update the things they include in Expo when they release a new SDK version. So I would not be surprised if SDK 37 required StackNavigator version 2.x instead of 1.x that is apparently required by SDK 36. The release notes should mention that when SDK 37 is released.

I agree, but I suspect it’s not necessary to do anything other than work with the existing version fields in app.json. This is not my area of expertise :sweat_smile:

You might want to check @llamaluvr’s posts about versioning on the forums.

See also the following for a way to increment the various version/build numbers when you release a new version of your app. I haven’t tried it yet, but it looks useful:

A bit more about OTA updates:

Have a look at the Publishing docs.

Then look into release channels:

You can publish your production version to the default release channel and build your App Store app with the default release channel too.
Then build another app that uses another release channel. e.g. called staging for testing your changes. You could perhaps have this version of the app on Test Flight. As you make changes, instead of building a new app, publish the changes to the staging release channel. Then the “staging” version of your app will pick up those changes, but the App Store version will not.

When you’re happy with the latest version you can promote it from the staging release channel to the default release channel for the App Store app to pick up.

The “elephant in the room” section is specifically about the Bare and ExpoKit workflows. Not the Managed workflow. Currently, OTA updates work in the managed workflow and I think in ExpoKit but not in the Bare workflow.

By this:

OTA updates and notifications were included in the SDK36 project on Github and were expected to be available in this release.

he means that OTA updates were going to be made available for apps built with the Bare workflow in SDK 36 and hopefully now with SDK 37. The managed workflow has had OTA updates for ages (if not for as long as it’s existed.)

Chiming in here regarding:

Build the app and get it on the App Store. Check what is displayed for all of these. Then update the version numbers, publish an OTA update and check all the versions displayed in the app again. I think that would make it clear what is going on. I’m sure @charliecruzan could clarify all of this without you having to go to all this trouble, though

Constants.nativeAppVersion is the value for the version field at the time of the build. Meaning this will not change with subsequent OTA updates.
Constants.nativeBuildVersion is the value for ios.buildNumber and android.versionCode in app.json, at the time of the build, so these should also not change with OTA updates. (same applies to platform.ios.buildNumber)

Values from Constants.manifest however, are what changes with OTA updates, and these are pulled from app.json at the time expo publish is run. So let’s say you build your app with version: 1.0.0 in app.json, then publish an OTA update with version: 1.0.1 in app.json. For your standalone app:

  • Constants.nativeAppVersion = “1.0.0”
  • Constants.manifest.version = “1.0.1”

@wodin’s done a great job already of explaining this, just wanted to clarify that he’s correct :slight_smile:

1 Like

Thanks, Charlie, for your clarification.
This is the version number I am trying to display within the app.
The number is typed into App Store Connect when publishing a new release of the app to the public.

Thanks, Charlie. I wasn’t quite sure of all of those details.

I’ve just looked at this again and realised I was mistaken. All that’s needed is to follow the updated React Navigation install instructions:

expo install react-native-gesture-handler react-native-reanimated react-native-screens react-native-safe-area-context @react-native-community/masked-view

Before the above I also ran expo install react-navigation. The changes to my package.json look like this:

diff --git package.json package.json
index e2777e5..ac126d8 100644
--- package.json
+++ package.json
@@ -9,6 +9,7 @@
   "dependencies": {
     "@expo/vector-icons": "^10.0.6",
+    "@react-native-community/masked-view": "0.1.5",
     "expo": "^36.0.0",
     "expo-asset": "~8.0.0",
     "expo-av": "~8.0.0",
@@ -19,12 +20,14 @@
     "react-dom": "16.9.0",
     "react-native": "",
     "react-native-gesture-handler": "~1.5.0",
-    "react-native-paper": "^3.3.0",
+    "react-native-paper": "^3.5.1",
+    "react-native-reanimated": "~1.4.0",
+    "react-native-safe-area-context": "0.6.0",
     "react-native-screens": "2.0.0-alpha.12",
     "react-native-web": "^0.11.7",
-    "react-navigation": "^4.0.10",
+    "react-navigation": "^4.1.0",
     "react-navigation-backhandler": "^1.3.2",
-    "react-navigation-stack": "^1.10.3"
+    "react-navigation-stack": "^2.0.16"
   "devDependencies": {
     "babel-preset-expo": "^8.0.0",

This worked for me, but it complained about some use of headerRight: <SomeComponent /> that I needed to change to headerRight: () => <SomeComponent /> to get rid of the warnings.

Thanks @wodin for tip with OTA. I had no idea that expo offered it. It’s taken a while (ICT people really don’t like to update their guides!) but I have the app checking for an update every now and then and grabbing the latest version of itself, with some help from your answers to in another forum topic.

1 Like

This topic was automatically closed 20 days after the last reply. New replies are no longer allowed.