Skip to main content
This guide will walk you through creating a home screen widget for your Expo app. You’ll go from zero to a working widget that displays data from your React Native app.
Before starting, ensure you have CocoaPods 1.16.2+, Xcode 16+, and Expo SDK 53+ installed. See the installation guide for details.

Create your first widget

1

Generate a widget target

Run the create-target CLI in your Expo project root:
npx create-target widget
This command will:
  • Create a /targets/widget directory with Swift template files
  • Install the @bacons/apple-targets package
  • Add the Config Plugin to your app.json
  • Generate an expo-target.config.js file
You can also use npx create-target without arguments to see a list of all available target types.
2

Configure your Apple Team ID (optional)

If you know your Apple Team ID, add it to your app.json:
app.json
{
  "expo": {
    "ios": {
      "appleTeamId": "QQ57RJ5UTD",
      "bundleIdentifier": "com.yourcompany.yourapp"
    },
    "plugins": ["@bacons/apple-targets"]
  }
}
You can find your Team ID in Xcode under Signing & Capabilities. If you don’t have it now, you can set it later.
3

Generate the Xcode project

Run prebuild to generate your iOS project with the widget target:
npx expo prebuild -p ios --clean
This creates the Xcode project and links your widget files to the expo:targets/widget virtual folder.
4

Open in Xcode

Open your project in Xcode:
xed ios
Navigate to the expo:targets folder in the project navigator. You’ll see your widget files there. Any changes you make here will be saved to /targets/widget in your project root.
5

Customize your widget

Edit the widget configuration at /targets/widget/expo-target.config.js:
targets/widget/expo-target.config.js
/** @type {import('@bacons/apple-targets/app.plugin').Config} */
module.exports = {
  type: "widget",
  icon: "./assets/widget-icon.png",
  colors: {
    $accent: "#007AFF",
    $widgetBackground: "#F0F0F0",
  },
};
The $accent color is used for button tints when editing the widget. The $widgetBackground sets the widget’s background color.
6

Build and test

In Xcode:
  1. Select the widget target from the scheme dropdown
  2. Choose a simulator or device
  3. Click Run (⌘R)
The widget will appear on your home screen. Long-press to add it if it doesn’t show up automatically.
If the widget doesn’t appear on the home screen, make sure you’re using iOS 18+. You can long-press the app icon and select widget display options.

Share data between your app and widget

Widgets can display data from your React Native app using App Groups and NSUserDefaults.

Configure App Groups

First, define an App Group in your app.json:
app.json
{
  "expo": {
    "ios": {
      "entitlements": {
        "com.apple.security.application-groups": ["group.com.yourcompany.yourapp"]
      }
    }
  }
}
Then mirror it in your widget config:
targets/widget/expo-target.config.js
/** @type {import('@bacons/apple-targets/app.plugin').ConfigFunction} */
module.exports = (config) => ({
  type: "widget",
  colors: {
    $accent: "#007AFF",
  },
  entitlements: {
    "com.apple.security.application-groups":
      config.ios.entitlements["com.apple.security.application-groups"],
  },
});

Set data from React Native

In your Expo app, use the ExtensionStorage API:
App.tsx
import { ExtensionStorage } from "@bacons/apple-targets";

// Create a storage instance with your App Group ID
const storage = new ExtensionStorage("group.com.yourcompany.yourapp");

export default function App() {
  const [count, setCount] = React.useState(0);

  const incrementCount = () => {
    const newCount = count + 1;
    setCount(newCount);
    
    // Save to shared storage
    storage.set("count", newCount);
    
    // Reload the widget
    ExtensionStorage.reloadWidget();
  };

  return (
    <View>
      <Text>Count: {count}</Text>
      <Button title="Increment" onPress={incrementCount} />
    </View>
  );
}

Read data in Swift

In your widget’s Swift code, access the shared data:
targets/widget/widgets.swift
let defaults = UserDefaults(suiteName: "group.com.yourcompany.yourapp")
let count = defaults?.integer(forKey: "count") ?? 0

struct widgetEntryView: View {
    var entry: Provider.Entry
    
    var body: some View {
        VStack {
            Text("Count from app:")
            Text("\(count)")
                .font(.largeTitle)
        }
    }
}
Run npx expo prebuild --clean after changing the config, then rebuild in Xcode.

Next steps

Target configuration

Learn about colors, images, frameworks, and deployment targets

App Clips

Build instant app experiences with App Clips

Code signing

Understand code signing for extensions

Advanced topics

Explore CocoaPods, shared files, and more

Troubleshooting

Widget doesn’t show on home screen

  • Make sure you’re using iOS 18 or later
  • Long-press the app icon and select widget display options
  • Try clearing SwiftUI preview cache: xcrun simctl --set previews delete all

Build errors with SwiftUI previews

React Native ships uncompiled, which can cause Swift compiler issues. Try:
npx expo prebuild --template ./node_modules/@bacons/apple-targets/prebuild-blank.tgz --clean
This creates a build without React Native for faster development. Don’t use this for production.

Changes not appearing after prebuild

If you modify expo-target.config.js or app.json, you must run:
npx expo prebuild --clean
Any changes outside the expo:targets folder in Xcode will be overwritten by prebuild.

Build docs developers (and LLMs) love