iOS development
GoogleService-Info.plist
build schemes
app configuration
Xcode

Use different GoogleService-Info.plist for different build schemes

Master System Design with Codemia

Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.

Introduction

If your iOS app uses Firebase in more than one environment, you should not swap GoogleService-Info.plist by hand before every build. The robust setup is to keep one plist per environment and let Xcode copy the correct file for the active build configuration or scheme. That keeps development, staging, and production pointed at the right Firebase project without manual edits.

Organize the Firebase Config Files

Start by storing each plist under a distinct path in the repository. For example:

text
Config/Firebase/Debug/GoogleService-Info.plist
Config/Firebase/Staging/GoogleService-Info.plist
Config/Firebase/Release/GoogleService-Info.plist

Do not add all three plists to the app target as regular bundled resources. If you do, Xcode may copy multiple files with the same name into the bundle and the build becomes fragile.

The safer pattern is to keep them in the project navigator for reference, but copy exactly one of them during the build.

Tie Schemes to Build Configurations

A scheme chooses a build configuration. That means the easiest setup is:

  • 'Debug scheme uses Debug configuration'
  • 'Staging scheme uses Staging configuration'
  • 'Release scheme uses Release configuration'

Then create a user-defined build setting on the app target, for example FIREBASE_PLIST_PATH, and assign a different value per configuration:

text
Debug   $(SRCROOT)/Config/Firebase/Debug/GoogleService-Info.plist
Staging $(SRCROOT)/Config/Firebase/Staging/GoogleService-Info.plist
Release $(SRCROOT)/Config/Firebase/Release/GoogleService-Info.plist

This is better than hardcoding paths inside a shell script because the build settings stay visible in Xcode and can also live in .xcconfig files if your project uses them.

Copy the Right File During the Build

Add a Run Script Phase before Copy Bundle Resources or near the end of build phases. The script should copy the selected plist into the built app bundle under the fixed name Firebase expects.

bash
1set -euo pipefail
2
3if [ ! -f "$FIREBASE_PLIST_PATH" ]; then
4  echo "Missing Firebase plist at: $FIREBASE_PLIST_PATH"
5  exit 1
6fi
7
8cp "$FIREBASE_PLIST_PATH" \
9  "$TARGET_BUILD_DIR/$UNLOCALIZED_RESOURCES_FOLDER_PATH/GoogleService-Info.plist"
10
11echo "Copied Firebase config from $FIREBASE_PLIST_PATH"

The app still sees the usual GoogleService-Info.plist file name at runtime, but the source file now depends on the active configuration.

Optional .xcconfig Setup

If your project already uses configuration files, putting the plist path there keeps the Xcode UI cleaner.

xcconfig
// Debug.xcconfig
FIREBASE_PLIST_PATH = $(SRCROOT)/Config/Firebase/Debug/GoogleService-Info.plist
xcconfig
// Staging.xcconfig
FIREBASE_PLIST_PATH = $(SRCROOT)/Config/Firebase/Staging/GoogleService-Info.plist
xcconfig
// Release.xcconfig
FIREBASE_PLIST_PATH = $(SRCROOT)/Config/Firebase/Release/GoogleService-Info.plist

The copy script stays the same. Only the build setting changes.

Verify the Active Configuration at Runtime

If you want a quick sanity check during development, print the selected Firebase app options after startup:

swift
1import FirebaseCore
2
3if let app = FirebaseApp.app() {
4    print(app.options.googleAppID)
5    print(app.options.projectID ?? "no project id")
6}

That makes it obvious when a staging build accidentally points at production.

Why Schemes Alone Are Not Enough

A common misunderstanding is that schemes themselves directly choose files. They do not. Schemes choose targets, actions, and build configurations. The file selection logic still needs to live in build settings, scripts, or target membership.

That is why the stable solution is "scheme to configuration, configuration to plist path, script copies one file." Once you think in those layers, the setup is easy to maintain.

Common Pitfalls

The biggest mistake is adding every GoogleService-Info.plist to Copy Bundle Resources. That often produces duplicate-file warnings or unpredictable results depending on build order.

Another mistake is using one plist file name per environment, such as GoogleService-Info-Debug.plist, and forgetting to rename it to GoogleService-Info.plist inside the app bundle. Firebase expects the standard file name unless you configure it manually in code.

Teams also often duplicate schemes but forget to create matching build configurations. In that case, multiple schemes still use the same plist path and the environment separation is only cosmetic.

Finally, do not rely on developers remembering to drag the correct plist into the project before archiving. That workflow will eventually ship the wrong Firebase credentials.

Summary

  • Keep one GoogleService-Info.plist per environment in separate paths.
  • Map each build configuration to a FIREBASE_PLIST_PATH value.
  • Use a run script phase to copy exactly one plist into the bundle as GoogleService-Info.plist.
  • Schemes choose configurations; configurations choose the plist path.
  • Avoid manual file swapping and avoid copying multiple plist files into the app bundle.

Course illustration
Course illustration

All Rights Reserved.