Skip to main content

Installation

Start a new app with the navigation or add it to the app you have, run it, and check the setup with doctor.

This page adds the navigation to an app: a new one made by init, or the app you have, set up by setup. At the end, the app starts with its first screen in a native stack, and doctor reports no problems.

Requirements​

React Native
  • 0.85 or newer; checked on 0.87.
  • The New Architecture only.
iOS and iPadOS
  • Xcode 26 or newer, CocoaPods.
  • iOS 15.1 or newer, the minimum of React Native.
Android
  • JDK 17, the Android SDK the app builds with.
  • minSdk 24 or higher.
Not yet
  • Expo apps: the config plugin is planned.
  • The web is not a target.

Two ways to start​

A new app​

init makes a React Native app at the version the navigation is checked with, adds the package, runs setup and puts a starter in place of the template's App. Then it installs the dependencies and the pods.

Terminal
npx @apexnative/navigation init MyApp

Run the app from its folder:

Terminal
cd MyApp
npx react-native run-ios
npx react-native run-android

Home opens with an Open Details button. A tap pushes Details with the system transition; the swipe back or the back button returns to Home.

The starter is the app of the Quick start:

FileWhat it is
Navigation.tscreateNavigation with Home and Details in a stack.
src/screens/Home.tsxThe first screen, with a button that pushes Details.
src/screens/Details.tsxThe screen Home pushes.
navigation-env.d.tsTypes of the commands, from screens: see Typed commands.

init removes the template's App.tsx and its test. The screens render inside the navigation, and a test that renders one component alone doesn't start it.

The starters​

--template picks what the new app starts with. Each starter is a working app to grow, not a demo to delete.

Terminal
npx @apexnative/navigation init MyApp --template tabs
TemplateWhat the app starts with
stackThe default, above: Home in a stack, with a button that pushes Details.
tabsTabs of Home, Profile and Settings, each in a stack of its own; Home pushes Details. The tab icons are SF Symbols on iOS (house, person, gearshape), and on Android vector drawables of Material Symbols, written to android/app/src/main/res/drawable/.
authLogin first. Sign in puts the user into the app's context and switches to the tabs above, where Profile shows the user's name; Sign out in Settings switches back to Login.

auth writes these files besides the screens:

FileWhat it is
Navigation.tsThe roots signedOut (Login) and signedIn (the tabs), the context { user } with its type exported as AppContext, and initialRoot, which picks the first root from the context.
src/session.tssignIn(name) and signOut(): each sets the context and switches the root.
navigation-env.d.tsWith the context line, as Navigation.ts exports AppContext: see Typed commands.

Login takes a name only. Your own sign-in goes where the comment in src/session.ts says, and the session you keep is read in willMount, so a user who signed in before starts on the tabs; until then each launch starts on Login. The whole flow, with links that wait for the sign-in: Sign in and sign out.

The options of init​

The app it makes:

OptionWhat it does
--template nameThe starter: stack (the default), tabs or auth. See The starters.
--version vThe React Native of the app: a version or a tag. By default, the one the navigation is checked with.
--pm namenpm, yarn or bun. By default, the one the command runs with.
--directory pathThe folder of the app; its name by default.
--package-name idThe Android package and the iOS bundle id, such as com.example.app.
--title textThe name the app shows.

The steps it skips:

OptionWhat it does
--skip-installMakes the app without installing dependencies and pods.
--skip-podsInstalls the dependencies, not the pods.
--skip-git-initNo git repository.

The app you have​

  1. Add the package.

    $ npm install @apexnative/navigation
  2. Set the app up for the navigation. setup shows each change and asks before it writes. On a Mac it then installs the pods, when it changed a file of the iOS app or the pods don't have the navigation yet: pod install, or bundle install and bundle exec pod install when the app has a Gemfile. On Linux and Windows it says to run pod install on a Mac.

    Terminal
    npx apex-navigation setup
  3. Run the app.

    Terminal
    npx react-native run-ios
    npx react-native run-android

    Your App shows as before, now as the first screen of a native stack, named Home in the new Navigation.ts.

tip

--dry-run shows the changes and writes nothing; --yes applies them without asking; --skip-pods leaves the pods for later and prints the line to run. A file changed by hand stays as it is, and setup prints what to change in it. Every option of every command: The CLI.

What setup changes, and why​

In an app as the React Native template makes it, the window shows one React view, and all the navigation happens inside React. With this library the window shows the containers of the platform: a stack, tabs, modals, overlays. Every screen is a React view inside them. So the app hands its window and its startup to the navigation, and that is all the changes do.

The templateOne React view
With the navigationContainers of the platform
The window
One React viewShows one React root view that fills it.
Containers of the platformShows the root of the navigation; screens are React views inside its containers.
React Native
One React viewStarted by the activity or the app delegate for that one view.
Containers of the platformStarted by the navigation, one host for every screen.
Back press
One React viewGoes to React.
Containers of the platformGoes to the navigation first: it pops a screen or closes a modal. On the first screen, the system's back, with its animation.
Status bar
One React viewOne for the app.
Containers of the platformEach screen's own, from its options.

Android files​

FileWhat setup doesWhy
MainApplication.ktExtends NavigationApplication; keeps the template's loadReactNative(this) in onCreate, or adds it.It keeps one React host for all screens, made after loadReactNative(this) loaded React Native. Native screens made by codegen are registered there, before JS opens them.
MainActivity.ktExtends NavigationActivity.The activity holds the containers of the navigation instead of one React view. It gets the back press, edge-to-edge and configuration changes first.
AndroidManifest.xmlAdds android:enableOnBackInvokedCallback="true" to <application>.On the first screen the system plays its own animation back to the home screen as the user swipes back (predictive back). Android 16 turns it on for apps that target API 36; an app that targets less opts in here. A value the app set itself stays. See Back to the home screen.
styles.xmlAppTheme inherits Theme.ApexNavigation.The bars, tabs and search are Material 3 views and take their colors from the app's theme. This one is Material 3 in neutral grays, light and dark; change any color in the same file. A theme of the app's own stays as it is. See Android's theme.

The main activity keeps the template's android:configChanges="keyboard|keyboardHidden|orientation|screenLayout|screenSize|smallestScreenSize|uiMode". Without orientation, screenSize, smallestScreenSize, screenLayout or uiMode Android recreates the activity on a rotation, a resize, a fold or a light/dark switch, and every screen goes with it; doctor warns about it under Config changes.

iOS files​

FileWhat setup doesWhy
AppDelegate.swiftExtends ANNAppDelegate.It creates the window, puts the root of the navigation in it and starts React Native. Deep links that come before JS is ready wait for it.
SceneDelegate.swiftExtends ANNSceneDelegate. React Native 0.88 and newer only: the earlier templates have no scene delegate.The template's scene delegate would start React Native a second time. ANNSceneDelegate moves the navigation's window into the app's scene and passes the scene's links to the app delegate.
Info.plistSets UIViewControllerBasedStatusBarAppearance to true.Each screen sets its own status bar from its options; with false, only the app as a whole can.

JavaScript files​

These files are the same on both platforms.

FileWhat setup doesWhy
index.jsStarts the app through the navigation.AppRegistry of the package starts createNavigation instead of mounting one root component.
Navigation.tsNew: shows App as the first screen.The app keeps working as it was, now as the first screen of a stack.
App.tsxTakes out the template's <StatusBar>.React Native's StatusBar logs an error on iOS once the screens set the status bar.
metro.config.jsWraps the exported config in withApexNavigation.Fast Refresh: an edit of a screen updates the running app in place, with its state, instead of reloading it. See Fast Refresh.
navigation-env.d.tsNew: ties the commands to the screens of Navigation.ts.The commands come from the package and can't see the app's screens. This declaration links them, so names and props are checked. See Typed commands.

Fast Refresh​

setup wraps your metro.config.js in withApexNavigation; in a new app init has done it. By hand:

metro.config.js
const { withApexNavigation } = require('@apexnative/navigation/metro');

module.exports = withApexNavigation(mergeConfig(getDefaultConfig(__dirname), config));

It does two things. React Native 0.87's transformer doesn't apply React Refresh's Babel plugin (Metro no longer passes it hot), and without the plugin a component that its module doesn't export loses its state at every edit. And it makes the module that imports createNavigation accept hot updates, so an edit of the screens map or of the app's config updates the running app without a reload: the screens, the default options (when theme or options changed), the roots, the listeners and the link routes. What only the start reads isn't run again: the first root, willMount, the splash and the context. The app's module itself runs again, as a component's module does in Fast Refresh: code at its top level (an SDK's init, a listener added there) runs again too, so keep such code in a module of its own that doesn't change, or make it safe to run twice. The same holds for a module the app's module imports that isn't a component, such as a store: its edit runs it and the app's module again instead of reloading the app, so a store made at the top of that module is made anew while the screens that didn't run again keep the old one. Reload the app (R) after such an edit.

doctor warns under Fast Refresh when the Metro config doesn't use it. What to write in a screen file so that its edits update in place: Fast Refresh.

Typed commands​

navigation.push('Details', { props }) comes from the package, and the package doesn't know the screens of your app. One declaration links them: setup and init write it in navigation-env.d.ts at the root of the app.

navigation-env.d.ts
// Made by apex-navigation setup: the types of the app's screens
// for the navigation's commands. Don't edit.
declare module '@apexnative/navigation' {
interface Register {
app: typeof import('./Navigation').Navigation;
}
}
export {};
  • It doesn't change with the screens. It holds no list of screens, only typeof Navigation: a screen added to screens is typed the moment the file is saved, with no command and no build.
  • The context too. When Navigation.ts exports a type or an interface named AppContext, the file registers it next to app, with the line context: import('./Navigation').AppContext;: see Typing it. A type of another name isn't picked up.
  • setup keeps it up to date. It writes the file again when it no longer fits: Navigation.ts began to export AppContext, or index.js imports the navigation from another module or under another name. A renamed module or export is an error of TypeScript in this file, not a silent loss of the types; doctor warns about such a file under Typed commands, and --fix runs setup.
  • Don't edit it, and commit it. setup knows its own file by the first line, the comment. A file without that line is yours: setup leaves it as it is and prints what it would write.
  • export {} matters. It makes the file a module, so the declaration adds to the types of the package instead of replacing them.
  • A tsconfig.json with its own include gets the file added to it by setup, as one of the changes it shows. One that lists its files instead: setup prints the line to add.
  • No tsconfig.json (a JavaScript app): the file isn't made. Everything works; screen names are plain strings and props aren't checked. doctor checks the names the commands give instead: see Check everything with doctor.
note

This is how TypeScript links a package to the code of an app: the commands are imported from the package, so the app has to tell it about its screens once. Other libraries do the same with a file their tools generate.

Check everything with doctor​

doctor checks what the navigation needs in the app and on the machine, and gives a fix for each problem. Run it after setup, after an upgrade, and whenever a build fails for no clear reason.

Terminal · npx apex-navigation doctor
  App
  ✓ React Native     0.87.1, New Architecture
  ✓ Navigation       1.0.0-alpha.0
  ✓ Native files     MainApplication.kt · MainActivity.kt · AndroidManifest.xml · styles.xml · AppDelegate.swift · Info.plist
  ✓ Entry point      index.js
  ✓ Typed commands   navigation-env.d.ts
  Navigation
  ✓ Screens          2
  ✓ Links            myapp://
  Android
  ▲ JDK 17           installed, but JDK 18 is in use
      fix  export JAVA_HOME=$(/usr/libexec/java_home -v 17)
  ✓ Android SDK      SDK 37, build tools 37.0.0, NDK 27.1.12297006
  ✓ minSdk           24
  · Edge-to-edge     Android 15 and newer (targetSdk 36)
  iOS
  ✓ Xcode            27.0
  ✓ CocoaPods        1.15.2
  ✓ Pods             navigation 1.0.0-alpha.0
  ✓ iOS version      React Native's minimum
  Devices
  ✓ iOS simulator    iOS 27.0
  ✓ adb              1 device connected
  Conflicts
  ✓ None             

  ──────────────────────────────────────────────
  1 warning · 16 checks passed
GroupWhat it checks
AppReact Native and the New Architecture, the package, the files setup sets up, the entry point, React Native loaded twice, the status bar of the template, the generated code of native screens. Typed commands: navigation-env.d.ts there, pointing to the navigation, with its context line, and read by tsconfig.json. The splash screen made from its settings, with its logo inside the circle of Android's splash icon. The app icon made from its settings, and an iOS icon set with no image, which the App Store refuses.
NavigationFrom the app's code. Screens: no name given twice in screens and the groups, and no file of the screens' folder with a defineScreen or nativeScreen that no file imports, a screen made and never added. Links: each prefix of linking declared on both platforms, the declarations no prefix has, a main activity that isn't singleTask. Screen names, in a JavaScript app only: a name that push, replace, showModal or showInColumn gives and no screen has, with its file and line.
AndroidJDK 17 in use, the Android SDK, minSdk, how edge-to-edge works in the app.
iOSXcode 26 or newer, CocoaPods, the pods of the navigation and whether they are out of date, the iOS version of the Podfile.
DevicesA simulator and adb to run the app on.
ConflictsAnother navigation in the app, an Expo app.
tip

--fix makes the fixes the CLI has, for errors and for warnings: setup, links, icons, codegen and pod install, asking before each. --json prints the result as one JSON document, for CI, tools and AI agents. The command exits with 1 when a check finds an error; warnings leave it at 0.

Next​