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);
};

@denistepp check this answer. It worked for me.

Yeah, my suggestion above to remove/copy the assets is basically the same thing as the rsync in the Stack Overflow post. You would no longer need to copy the app.json file now that we have app.config.js.

Hi @wodin ,
But in this case you have to change the env variables each time when you want to change the app right ? But when you have a separate app.json for each app , you just need to do the build with relevant app parameter and app.json will be replaced.

There’s no real difference.

Stack Overflow answer:

$ expo start --config=some-app.json

With environment variable:

$ WHITE_LABEL_APP=some-app expo start

Ok seems like both are same.

./switch project1 && expo start

where switch is a bash command which replace the relevant directories (in this case app.json and assets)

Hello,

Thanks for these suggestions, one more questions how do you handle ÔTA updates ?

Hi @sabativi

As long as the owner and slug are the same for all apps (and they are all on the same SDK and release channel) and as long as you don’t need to update and of the things mentioned in the limitations, I think a single publish should update all of the apps.

Have you run into any problems?

Thanks, problem is I have differents icons, splash screens.
Those will be different apps on the store, so bundle identier also I guess
Everything else is the same.

Are channels a good idea, or I just have branches where I change app.json file and do a simple rebase of the main branch ?

Imagine you have 30 apps where you would need to push an update at the same time (new feature or smth). If you would update app.json every time for every single app = not a good idea. Channels are really good because you can write a simple script to update all your apps.

You could, of course, write a script to update base app.json and push changes for it on every iteration, but I imagine that would take longer…not sure :slight_smile:

My way is to have an updateScript.ext which would run publish command for every app using the correct config json file for each one of them. Hope it helps.

Thanks for this, but can I have different apps on stores without different app.json files and without different bundleIdentifier for iOS or package names for android ?
From what I know those need to be unique, no ?

yes they have to be unique.

Then I need to generate differents apps with app.json and I need to publish them separetly