Skip to main content

Configuration Files

Each target directory must contain either:
  • expo-target.config.js (JavaScript)
  • expo-target.config.json (JSON)
The JavaScript format is more flexible and supports functions for dynamic configuration.
ESM and TypeScript are not supported in target configs. Use CommonJS (module.exports and require()) for compatibility.

Basic Configuration

A minimal target configuration requires only the type property:
/** @type {import('@bacons/apple-targets/app.plugin').Config} */
module.exports = {
  type: "widget",
};

Complete Schema

Here’s the full configuration schema with all available options:
/** @type {import('@bacons/apple-targets/app.plugin').Config} */
module.exports = {
  // Required: Type of extension to generate
  type: "widget",

  // Optional: Name of the target (defaults to directory name)
  name: "my_widget",

  // Optional: Custom display name for CFBundleDisplayName
  displayName: "My Widget",

  // Optional: Bundle identifier
  // If prefixed with ".", appends to main app's bundle ID
  bundleIdentifier: ".mywidget",

  // Optional: Icon path (local file or URL)
  icon: "./icon.png",

  // Optional: Additional frameworks to link
  frameworks: ["UserNotifications", "Intents"],

  // Optional: iOS deployment target (defaults to 18.0)
  deploymentTarget: "15.1",

  // Optional: Apple Team ID for signing
  appleTeamId: "TEAM123456",

  // Optional: Entitlements configuration
  entitlements: {
    "com.apple.security.application-groups": ["group.com.example.app"],
  },

  // Optional: Color assets for the target
  colors: {
    $accent: "steelblue",
    $widgetBackground: "dodgerblue",
    customColor: { light: "#FF0000", dark: "#0000FF" },
  },

  // Optional: Image assets for the target
  images: {
    logo: "./assets/logo.png",
    banner: "https://example.com/banner.png",
    retina: {
      "1x": "./[email protected]",
      "2x": "./[email protected]",
      "3x": "./[email protected]",
    },
  },

  // Optional: Export JS bundle for React Native targets
  exportJs: false,
};

Configuration Reference

type (required)

Type: ExtensionType Specifies the type of Apple target to create. Must be one of 50+ supported types.
type: "widget" // or "clip", "share", "intent", etc.
  • widget - Home screen widget
  • clip - App Clip
  • share - Share Extension
  • action - Headless action (share sheet)
  • notification-content - Notification Content Extension
  • notification-service - Notification Service Extension
  • intent - Siri Intent Extension
  • intent-ui - Siri Intent UI Extension
  • safari - Safari Extension
  • watch - Watch App
  • watch-widget - Watch Face Complication
  • And 40+ more…

name

Type: string (optional)
Default: Directory name
The internal name of the target. Used for the Xcode target name and product name.
name: "my_widget"
The name is sanitized for Xcode compatibility (from with-widget.ts:44-56):
const productName =
  sanitizeNameForNonDisplayUse(props.name || targetDirName) ||
  sanitizeNameForNonDisplayUse(targetDirName) ||
  sanitizeNameForNonDisplayUse(props.type);

displayName

Type: string (optional)
Default: name value
User-facing display name set as CFBundleDisplayName in Info.plist.
displayName: "My Awesome Widget"

bundleIdentifier

Type: string (optional)
Default: Auto-generated from main app’s bundle ID
The bundle identifier for the target. Supports relative notation with dot prefix:
// Relative: appends to main app's bundle ID
bundleIdentifier: ".mywidget"
// If main app is "com.example.app", becomes "com.example.app.mywidget"

// Absolute: uses exactly as specified
bundleIdentifier: "com.example.widget"
For App Clips, the default is {mainBundleId}.clip (from with-widget.ts:299-302):
if (props.type === "clip") {
  return mainAppBundleId + ".clip";
}

icon

Type: string (optional) Path to icon image. Supports both local paths and URLs:
// Local file (relative to target directory)
icon: "./icon.png"

// Absolute path
icon: "../../assets/icon.png"

// URL
icon: "https://github.com/expo.png"
The icon is automatically processed and resized for the target’s asset catalog.

frameworks

Type: string[] (optional)
Default: Type-specific frameworks
Additional system frameworks to link. Appends to the default frameworks for the target type:
frameworks: ["UserNotifications", "Intents", "CoreLocation"]
Each target type has default frameworks defined in target.ts TARGET_REGISTRY. For example, widgets get:
frameworks: ["WidgetKit", "SwiftUI", "ActivityKit", "AppIntents"]

deploymentTarget

Type: string (optional)
Default: "18.0" (iOS), "11.0" (watchOS)
Minimum iOS/watchOS version required:
deploymentTarget: "15.1"
WatchOS targets automatically use DEFAULT_WATCHOS_DEPLOYMENT_TARGET (from with-widget.ts:331-333).

appleTeamId

Type: string (optional)
Default: From app.json ios.appleTeamId
Apple Team ID for code signing:
appleTeamId: "QQ57RJ5UTD"

entitlements

Type: Entitlements object (optional) Entitlements configuration for the target. See Entitlements documentation for details.
entitlements: {
  "com.apple.security.application-groups": ["group.com.example.app"],
  "com.apple.developer.healthkit": true,
  "com.apple.developer.associated-domains": ["appclips:example.com"],
}
Some targets have automatic entitlements. For example, App Clips automatically set com.apple.developer.parent-application-identifiers.

colors

Type: Record<string, string | DynamicColor> (optional) Generates color assets in the target’s asset catalog:
colors: {
  // Static color (hex or CSS color name)
  primary: "steelblue",
  
  // Dynamic color (light/dark mode)
  background: {
    light: "#FFFFFF",
    dark: "#000000",
  },
  
  // Special build setting colors
  $accent: "red",              // ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME
  $widgetBackground: "blue",   // ASSETCATALOG_COMPILER_WIDGET_BACKGROUND_COLOR_NAME
}
In Swift, access colors via:
Color("primary")          // Your custom color
Color("background")       // Adapts to light/dark mode

images

Type: Record<string, string | ImageSet> (optional) Generates image assets in the target’s asset catalog:
images: {
  // Simple image
  logo: "./assets/logo.png",
  
  // URL
  avatar: "https://github.com/evanbacon.png",
  
  // Multi-resolution
  icon: {
    "1x": "./[email protected]",
    "2x": "./[email protected]",
    "3x": "./[email protected]",
  },
}
In Swift, access images via:
Image("logo")
UIImage(named: "avatar")

exportJs

Type: boolean (optional)
Default: true for clip, false otherwise
Enables React Native JS bundle export for the target. When true, links the main app’s Metro bundler build phase:
exportJs: true
Only use exportJs: true for targets that run React Native code (App Clips, Share Extensions). Most extension types cannot run React Native.
From with-widget.ts:350-353:
exportJs:
  props.exportJs ??
  // Assume App Clips are used for React Native.
  props.type === "clip",

Function-Based Configuration

For dynamic configuration, export a function that receives the Expo config:
/** @type {import('@bacons/apple-targets/app.plugin').ConfigFunction} */
module.exports = (config) => ({
  type: "widget",
  
  // Sync app groups from main app
  entitlements: {
    "com.apple.security.application-groups":
      config.ios.entitlements["com.apple.security.application-groups"],
  },
  
  // Generate bundle ID from main app
  bundleIdentifier: `${config.ios.bundleIdentifier}.widget`,
  
  // Use environment variables
  deploymentTarget: process.env.MIN_IOS_VERSION || "18.0",
});

Real-World Examples

Widget with Live Activities

module.exports = {
  type: "widget",
  icon: "https://upload.wikimedia.org/wikipedia/commons/thumb/e/e1/FullMoon2010.jpg/1200px-FullMoon2010.jpg",
  colors: {
    $accent: "steelblue",
    $widgetBackground: "dodgerblue",
  },
};

App Clip with Associated Domains

module.exports = (config) => ({
  type: "clip",
  name: "clip",
  icon: "https://github.com/expo.png",
  entitlements: {
    "com.apple.developer.associated-domains": [
      "appclips:pillarvalley.expo.app",
    ],
  },
});

Share Extension with App Groups

module.exports = {
  type: "share",
  displayName: "Share to App",
  icon: "./assets/share-icon.png",
  frameworks: ["MobileCoreServices"],
  entitlements: {
    "com.apple.security.application-groups": ["group.com.example.app"],
  },
};

TypeScript Support

While the config file itself must be .js, you get full IntelliSense via JSDoc:
/** @type {import('@bacons/apple-targets/app.plugin').Config} */
module.exports = {
  // Full autocomplete for all properties
  type: "widget",
  entitlements: {
    // Autocomplete for entitlement keys
  },
};

Next Steps

Entitlements

Configure app groups and permissions

Development Workflow

Learn the prebuild and iteration workflow

Build docs developers (and LLMs) love