Skip to main content
This guide covers everything you need to install and configure Expo Apple Targets in your Expo project.

Prerequisites

Before installing Expo Apple Targets, ensure your development environment meets these requirements:

Required versions

All three of these are required for Expo Apple Targets to work correctly.
  • CocoaPods 1.16.2 or higher (requires Ruby 3.2.0+)
  • Xcode 16 or higher (requires macOS Sequoia 15+)
  • Expo SDK 53 or higher

Check your versions

Verify your current versions:
# Check CocoaPods version
pod --version

# Check Xcode version
xcodebuild -version

# Check Expo SDK version (in your project)
npx expo --version

Upgrade CocoaPods

If you need to upgrade CocoaPods:
# Install Ruby 3.2.0 or higher first (using rbenv)
rbenv install 3.2.0
rbenv global 3.2.0

# Then upgrade CocoaPods
gem install cocoapods

Upgrade Expo SDK

To upgrade to Expo SDK 53+:
npx expo install expo@latest
npx expo install --fix

Installation

1

Install the package

The easiest way to install Expo Apple Targets is using the create-target CLI, which automatically installs dependencies:
npx create-target
Select a target type (we recommend starting with widget) and the CLI will:
  • Install @bacons/apple-targets
  • Add the Config Plugin to your app.json
  • Generate a /targets directory with template files
  • Create an expo-target.config.js file
You can also specify the target type directly: npx create-target widget
2

Manual installation (alternative)

If you prefer to install manually:
npm install @bacons/apple-targets
Then add the plugin to your app.json:
app.json
{
  "expo": {
    "plugins": ["@bacons/apple-targets"]
  }
}
You’ll still need to create target directories and configuration files manually.
3

Configure bundle identifier and Team ID

In your app.json, set your iOS bundle identifier and Apple Team ID:
app.json
{
  "expo": {
    "name": "My App",
    "slug": "my-app",
    "ios": {
      "bundleIdentifier": "com.yourcompany.yourapp",
      "appleTeamId": "QQ57RJ5UTD"
    },
    "plugins": ["@bacons/apple-targets"]
  }
}
You can find your Apple Team ID in Xcode:
  1. Open any iOS project in Xcode
  2. Select your target
  3. Go to Signing & Capabilities
  4. Your Team ID appears next to your team name
4

Generate the iOS project

Run prebuild to generate your iOS project:
npx expo prebuild -p ios --clean
This creates the ios/ directory with your Xcode project and links any targets from /targets.
5

Open in Xcode

Open your project in Xcode:
xed ios
You should see your targets in the expo:targets virtual folder in the project navigator.

Project structure

After installation, your project will have this structure:
your-expo-app/
├── app.json
├── package.json
├── targets/                    # Target source files (never in git-ignored ios/)
│   └── widget/                 # Each target has its own directory
│       ├── expo-target.config.js   # Target configuration
│       ├── index.swift             # Main widget bundle
│       ├── widgets.swift           # Widget implementation
│       ├── AppIntent.swift         # Widget intents
│       ├── Info.plist              # Not managed, freely editable
│       └── Assets.xcassets/        # Widget assets
└── ios/                        # Generated by prebuild (git-ignored)
    └── YourApp.xcodeproj
        └── expo:targets/       # Virtual folder linking to /targets
The /targets directory is not git-ignored and should be committed to your repository. The ios/ directory is generated and should remain git-ignored.

Target configuration

Each target requires an expo-target.config.js or expo-target.config.json file:
targets/widget/expo-target.config.js
/** @type {import('@bacons/apple-targets/app.plugin').Config} */
module.exports = {
  type: "widget",
  
  // Optional: Override the target name (defaults to directory name)
  name: "my_widget",
  
  // Optional: Display name (defaults to name)
  displayName: "My Widget",
  
  // Optional: Icon for the target
  icon: "../assets/icon.png",
  
  // Optional: Colors (generates xcassets)
  colors: {
    $accent: "#007AFF",
    $widgetBackground: "#F0F0F0",
  },
  
  // Optional: Images (generates xcassets)
  images: {
    logo: "../assets/logo.png",
  },
  
  // Optional: Frameworks to link
  frameworks: ["WidgetKit", "SwiftUI"],
  
  // Optional: Deployment target (defaults to 18.0)
  deploymentTarget: "15.1",
  
  // Optional: Bundle identifier (defaults to main app + target name)
  bundleIdentifier: ".mywidget",
  
  // Optional: Entitlements
  entitlements: {
    "com.apple.security.application-groups": ["group.com.yourcompany.yourapp"],
  },
  
  // Optional: Export JS bundle for React Native targets (App Clips, Share Extensions)
  exportJs: false,
};

Dynamic configuration

You can also export a function to access the Expo config:
targets/widget/expo-target.config.js
/** @type {import('@bacons/apple-targets/app.plugin').ConfigFunction} */
module.exports = (config) => ({
  type: "widget",
  colors: {
    $accent: "steelblue",
  },
  entitlements: {
    // Mirror app groups from main app
    "com.apple.security.application-groups":
      config.ios.entitlements["com.apple.security.application-groups"],
  },
});
ESM and TypeScript are not supported in target config files. Use require() for sharing variables across targets.

Special colors

Certain color names map to Xcode build settings:
Color NameBuild SettingPurpose
$accentASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAMEGlobal accent color (button tints, etc.)
$widgetBackgroundASSETCATALOG_COMPILER_WIDGET_BACKGROUND_COLOR_NAMEWidget background color
Colors can be defined as CSS strings or with separate light/dark values:
colors: {
  // Simple color
  $accent: "#007AFF",
  
  // Light and dark mode
  $widgetBackground: {
    color: "#FFFFFF",
    darkColor: "#000000",
  },
}

Advanced: CocoaPods customization

To customize CocoaPods settings for a target, add a pods.rb file in the target directory:
targets/clip/pods.rb
require File.join(File.dirname(`node --print "require.resolve('react-native/package.json')"`), "scripts/react_native_pods")

exclude = []
use_expo_modules!(exclude: exclude)

config_command = [
  'node',
  '--no-warnings',
  '--eval',
  'require(require.resolve(\'expo-modules-autolinking\', { paths: [require.resolve(\'expo/package.json\')] }))(process.argv.slice(1))',
  'react-native-config',
  '--json',
  '--platform',
  'ios'
]

config = use_native_modules!(config_command)

use_react_native!(
  :path => config[:reactNativePath],
  :hermes_enabled => podfile_properties['expo.jsEngine'] == 'hermes',
  :app_path => "#{Pod::Config.instance.installation_root}/.."
)
This is useful for enabling React Native in App Clip or Share Extension targets.

Sharing files between targets

To share files between targets and the main app:
  • Target-specific shared files: Add a _shared/ directory inside a target (e.g., targets/widget/_shared/)
  • Global shared files: Add a targets/_shared/ directory at the root level
Files in _shared/ directories are automatically linked to both the target and the main app.
You must run npx expo prebuild --clean whenever you add, rename, or remove files in _shared/ directories.

Verify installation

To verify everything is set up correctly:
1

Check target appears in Xcode

Open your project in Xcode (xed ios) and verify:
  • Your target appears in the expo:targets folder
  • The target appears in the scheme dropdown
2

Build the target

In Xcode:
  1. Select your target from the scheme dropdown
  2. Select a simulator
  3. Press ⌘R to build and run
The target should build successfully.
3

Test prebuild

Make a change to your expo-target.config.js (e.g., change a color):
npx expo prebuild -p ios --clean
Open Xcode and verify the change appears in the generated project.

Next steps

Quickstart

Build your first widget in minutes

Target configuration

Learn about all configuration options

Supported targets

View all 40+ supported target types

Troubleshooting

Common issues and solutions

Common issues

CocoaPods version too old

Error: [!] CocoaPods 1.16.2 or higher is required Solution: Upgrade CocoaPods and Ruby:
rbenv install 3.2.0
rbenv global 3.2.0
gem install cocoapods

Xcode version too old

Error: Build failures with Widget or SwiftUI code Solution: Upgrade to Xcode 16 or higher (requires macOS Sequoia). Download from the Mac App Store or Apple Developer.

Target not appearing in Xcode

Solution:
  1. Ensure you ran npx expo prebuild -p ios --clean
  2. Close and reopen Xcode
  3. Check that the target directory has an expo-target.config.js file

Changes not persisting

Problem: Edits in Xcode are lost after running prebuild Solution: Only edit files inside the expo:targets/ virtual folder. Changes elsewhere in the Xcode project will be overwritten.

Build docs developers (and LLMs) love