How to Create White-Label Project using Expo React Native

I am wondering how to create a white-label project in using react native without ejecting from expo. I understand how to use dynamic styles based on settings/configuration that I choose, but I do not get how to compile and publish the project with app.json matching those settings. Should I create multiple app.json files and use them for each setting/project I have?

Have a look at using app.config.js instead of creating multiple app.json files.

Then you could e.g. use environment variables to choose which version of the app to build.

I have read the docs but I still don’t quite understand how can we use app.config to set env variables for multiple apps, especially contents in app.json without creating another file? Can you please give an example how would you set it up if I have, for instance, 2 projects that are the same but only need to differ in color and package name/app name?

@denistepp I have not put a lot of thought into this, so don’t take this as “the way things should be done”, but here’s something that works with an app.json containing the common stuff and app.config.js containing per-app overrides:

$ expo init white-label
...
$ cd white-label
$ expo add expo-constants
$ yarn add -D lodash

$ cat app.config.js

import { merge } from 'lodash';

const apps = {
  app1: {
    name: 'App One',
    splash: {
      backgroundColor: '#dc9d13',
    },
  },
  app2: {
    name: 'App Two',
    splash: {
      backgroundColor: '#13d9dc',
    },
  },
};

export default ({ config }) => {
  const app = apps[process.env.WHITE_LABEL_APP];

  return merge({}, config, app, { extra: { backgroundColor: app.splash.backgroundColor } });
};

$ cat App.js

import { StatusBar } from 'expo-status-bar';
import React from 'react';
import { StyleSheet, Text, View } from 'react-native';
import Constants from 'expo-constants';

export default function App() {
  return (
    <View style={styles.container}>
      <Text>Open up App.js to start working on your app!</Text>
      <StatusBar style="auto" />
    </View>
  );
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: Constants.manifest.extra.backgroundColor,
    alignItems: 'center',
    justifyContent: 'center',
  },
});

Then you can set the WHITE_LABEL_APP environment variable to app1 or app2 depending on which one you want to run/build/etc.

e.g.:
$ WHITE_LABEL_APP=app1 expo start

And instead of storing the overrides in app.config.js you could potentially store them in a database or whatever you like.

Thank you for your response! I will definitely try this approach. And what about assets? Can I use the same approach to define different assets for different applications? From what I see now, I would need to import all my assets in config file and then use them as in your example something like this:

const apps = {
  app1: {
    name: 'App One',
    splash: {
      backgroundColor: '#dc9d13',
    },
    extra:{
        someImage: require('./assets/imageApp1.png')
    }
  },
  app2: {
    name: 'App Two',
    splash: {
      backgroundColor: '#13d9dc',
    },
    extra:{
        someImage: require('./assets/imageApp2.png')
    }
  },
};

Will it exclude the unnecessary imports during compilation? Or all my assets will be bundled in the end into larger app? :frowning:

Also how does it know what package name, slug, and all other stuff from app.json to use for which app? do I have to fill const apps with contents of app.json for each “app”?

hmm… not sure off hand how best to deal with assets. I suppose one (ugly) approach could be to do something like this in app.config.js:

  • Remove the assets directory
  • Copy appN/assets to assets

I’d suggest symlinks instead of the above, but if I remember correctly metro has trouble with symlinks.

Then your app’s code could refer to e.g. ./assets/logo.png as normal.

My example assumes that app.json would contain everything from a normal app.json that is common across all of the apps (which I assume would be most of it). Everything you want to override you put in app.config.js. But you could e.g. use the WHITE_LABEL_APP environment variable to construct a slug, android.package and ios.bundleIdentifier.

e.g.:

import { merge } from 'lodash';

const apps = {
  app1: {
    name: 'App One',
    splash: {
      backgroundColor: '#dc9d13',
    },
  },
  app2: {
    name: 'App Two',
    splash: {
      backgroundColor: '#13d9dc',
    },
  },
};

export default ({ config }) => {
  const app = apps[process.env.WHITE_LABEL_APP];

  // This assumes that there are no dashes or spaces etc. in the WHITE_LABEL_APP env var
  const android = {
    android: {
      package: `com.example.whitelabel.${process.env.WHITE_LABEL_APP}`,
    },
  };
  const ios = {
    ios: {
      bundleIdentifier: `com.example.whitelabel.${process.env.WHITE_LABEL_APP}`,
    },
  };
  const slug = { slug: process.env.WHITE_LABEL_APP };

  const extra = { extra: { backgroundColor: app.splash.backgroundColor } };

  return merge({}, config, app, slug, android, ios, extra);
};