Skip to main content
The ExtensionStorage class provides a JavaScript API for storing and retrieving data in App Groups, enabling communication between your main app and extensions (widgets, app clips, share extensions, etc.).

Overview

ExtensionStorage wraps iOS’s UserDefaults with app group support, making it easy to share data between your React Native app and native extensions. It’s commonly used to pass data to widgets or receive data from share extensions.

Installation

npm install @bacons/apple-targets
The native module is automatically linked when you run expo prebuild.

Basic Usage

import { ExtensionStorage } from '@bacons/apple-targets';

// Create an instance with your app group identifier
const storage = new ExtensionStorage('group.com.company.app');

// Store data
storage.set('widgetData', { count: 42, message: 'Hello' });

// Retrieve data
const data = storage.get('widgetData');

// Reload widgets after updating data
ExtensionStorage.reloadWidget();

Constructor

appGroup
string
required
The app group identifier used for sharing data.Example:
const storage = new ExtensionStorage('group.com.company.app');
Important: The app group must be configured in your target’s entitlements:
// expo-target.config.js
entitlements: {
  "com.apple.security.application-groups": ["group.com.company.app"]
}

Instance Methods

set()

Stores a value in the app group storage.
set(key: string, value?: string | number | Record<string, string | number> | Array<Record<string, string | number>>): void
key
string
required
The key to store the value under.
value
string | number | object | array | null
The value to store. Pass null or undefined to remove the key.Supported types:
  • string: Stored as a string
  • number: Stored as an integer
  • object: Stored as a dictionary (string/number values only)
  • array: Stored as an array of dictionaries
  • null / undefined: Removes the key
Examples:
storage.set('message', 'Hello Widget');

get()

Retrieves a value from the app group storage.
get(key: string): string | null
key
string
required
The key to retrieve.
return
string | null
The stored value as a string, or null if the key doesn’t exist.Note: All values are returned as strings. For objects and arrays, you’ll need to parse them:
const dataStr = storage.get('widgetData');
const data = dataStr ? JSON.parse(dataStr) : null;
Example:
const message = storage.get('message');
console.log(message); // "Hello Widget"

const dataStr = storage.get('widgetData');
if (dataStr) {
  const data = JSON.parse(dataStr);
  console.log(data.title); // "My Title"
}

remove()

Removes a key from the app group storage.
remove(key: string): void
key
string
required
The key to remove.
Example:
storage.remove('widgetData');
Note: This is equivalent to calling storage.set(key, null).

Static Methods

reloadWidget()

Reloads all widgets or a specific widget kind.
static reloadWidget(name?: string): void
name
string
Optional widget kind to reload. If omitted, reloads all widgets.
Examples:
// Reload all widgets
ExtensionStorage.reloadWidget();

// Reload a specific widget kind
ExtensionStorage.reloadWidget('MyWidget');
Important: Call this after updating data that your widgets depend on. This triggers iOS to refresh the widget’s timeline.

reloadControls()

Reloads all control widgets or a specific control widget kind.
static reloadControls(name?: string): void
name
string
Optional control widget kind to reload. If omitted, reloads all control widgets.
Examples:
// Reload all control widgets
ExtensionStorage.reloadControls();

// Reload a specific control widget kind
ExtensionStorage.reloadControls('MyControl');
Note: Control widgets are a special type of widget introduced in iOS 18 for Lock Screen and Control Center.

Complete Example

Here’s a complete example showing how to use ExtensionStorage to share data with a widget:
import { ExtensionStorage } from '@bacons/apple-targets';
import { useEffect, useState } from 'react';
import { Button, Text, View } from 'react-native';

const storage = new ExtensionStorage('group.com.company.app');

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

  const updateWidget = () => {
    const newCount = count + 1;
    setCount(newCount);

    // Store data for the widget
    storage.set('widgetData', {
      count: newCount,
      updated: new Date().toISOString()
    });

    // Reload the widget to show new data
    ExtensionStorage.reloadWidget('MyWidget');
  };

  return (
    <View>
      <Text>Count: {count}</Text>
      <Button title="Update Widget" onPress={updateWidget} />
    </View>
  );
}

App Groups Setup

To use ExtensionStorage, you need to configure app groups in both your main app and extension targets.

Main App

Add app groups to your app.json:
{
  "expo": {
    "ios": {
      "entitlements": {
        "com.apple.security.application-groups": ["group.com.company.app"]
      }
    }
  }
}

Extension Target

Add app groups to your target’s expo-target.config.js:
module.exports = {
  type: "widget",
  entitlements: {
    "com.apple.security.application-groups": ["group.com.company.app"]
  }
};
Important: Both targets must use the exact same app group identifier.

Best Practices

Use Consistent Keys

Define your keys as constants to avoid typos:
const STORAGE_KEYS = {
  WIDGET_DATA: 'widgetData',
  USER_SETTINGS: 'userSettings',
} as const;

storage.set(STORAGE_KEYS.WIDGET_DATA, data);

Always Reload After Updates

Remember to reload widgets after updating their data:
storage.set('widgetData', newData);
ExtensionStorage.reloadWidget(); // Don't forget this!

Handle Missing Data

Always check if data exists before parsing:
const dataStr = storage.get('widgetData');
if (dataStr) {
  try {
    const data = JSON.parse(dataStr);
    // Use data
  } catch (error) {
    console.error('Failed to parse widget data:', error);
  }
}

Keep Data Small

App Group storage is limited. Keep stored data small and only include what’s necessary for your extensions.

TypeScript Types

class ExtensionStorage {
  constructor(appGroup: string);
  
  set(
    key: string,
    value?:
      | string
      | number
      | Record<string, string | number>
      | Array<Record<string, string | number>>
  ): void;
  
  get(key: string): string | null;
  
  remove(key: string): void;
  
  static reloadWidget(name?: string): void;
  
  static reloadControls(name?: string): void;
}

Build docs developers (and LLMs) love