The images property in your target configuration allows you to add custom image assets to the target’s Assets.xcassets. These images can be used in your Swift code and support multiple scale factors (@1x, @2x, @3x).
Basic Usage
Define images in your expo-target.config.js:
module.exports = {
type: "widget",
images: {
// Single image (auto-generates all scales)
logo: "./assets/logo.png",
// Image from URL
avatar: "https://github.com/evanbacon.png",
// Multiple scales
icon: {
"1x": "./assets/icon.png",
"2x": "./assets/[email protected]",
"3x": "./assets/[email protected]"
}
}
};
Image Sources
Local File Paths
Provide a relative or absolute path to a local image file:
images: {
logo: "./assets/logo.png",
background: "./images/bg.jpg",
icon: "/absolute/path/to/icon.png"
}
Supported formats:
- PNG (
.png)
- JPEG (
.jpg, .jpeg)
- SVG (
.svg)
- Other formats supported by
@expo/image-utils
Remote URLs
Provide a URL to download the image:
images: {
avatar: "https://github.com/evanbacon.png",
banner: "https://example.com/banner.jpg"
}
Images are downloaded and cached during the prebuild process.
Multiple Scales
Provide different images for each scale factor:
Image for 1x scale (standard resolution). Used on non-Retina displays.
Image for 2x scale (Retina displays). This is the most common resolution for modern iOS devices.
Image for 3x scale (Retina HD displays). Used on iPhone Plus, Pro, and Pro Max models.
Note: If you provide only one scale, iOS will use it for all scales. For best quality, provide all three scales.
Using Images in Swift
Images are accessible as named assets in SwiftUI and UIKit:
import SwiftUI
struct MyWidgetView: View {
var body: some View {
VStack {
// Simple image
Image("logo")
.resizable()
.aspectRatio(contentMode: .fit)
.frame(width: 100, height: 100)
// Image with system symbol fallback
if let avatarImage = UIImage(named: "avatar") {
Image(uiImage: avatarImage)
.clipShape(Circle())
} else {
Image(systemName: "person.circle.fill")
}
}
}
}
import UIKit
// Load image
let logoImage = UIImage(named: "logo")
let avatarImage = UIImage(named: "avatar")
// Use in image view
let imageView = UIImageView(image: logoImage)
imageView.contentMode = .scaleAspectFit
view.addSubview(imageView)
// Check if image exists
if let image = UIImage(named: "background") {
view.backgroundColor = UIColor(patternImage: image)
}
The system automatically selects the appropriate scale based on the device’s screen resolution.
Image Asset Generation
When you run expo prebuild, the plugin automatically:
- Creates an
Assets.xcassets directory in your target
- Generates an
.imageset subdirectory for each image
- Downloads remote images and caches them
- Generates missing scales if only one image is provided
- Creates a
Contents.json file with the image metadata
The generated structure looks like this:
targets/
MyWidget/
Assets.xcassets/
logo.imageset/
1x.png
2x.png
3x.png
Contents.json
avatar.imageset/
1x.png
Contents.json
TypeScript Type Definitions
type ImageConfig = Record<
string,
string | {
"1x"?: string;
"2x"?: string;
"3x"?: string;
}
>;
Complete Example
Here’s a complete example showing how to define and use images in a widget:
// targets/MyWidget/expo-target.config.js
module.exports = {
type: "widget",
images: {
// App logo - single file, auto-scales
logo: "./assets/logo.png",
// Background image from URL
background: "https://example.com/widget-bg.jpg",
// Avatar from GitHub
avatar: "https://github.com/evanbacon.png",
// Custom icon with all scales
statusIcon: {
"1x": "./assets/status-icon.png",
"2x": "./assets/[email protected]",
"3x": "./assets/[email protected]"
},
// Chart image
chart: "./assets/chart.png",
// Decorative elements
star: "./assets/star.png",
badge: "./assets/badge.png"
}
};
import SwiftUI
import WidgetKit
struct MyWidgetView: View {
var entry: Provider.Entry
var body: some View {
ZStack {
// Background image
if let bgImage = UIImage(named: "background") {
Image(uiImage: bgImage)
.resizable()
.aspectRatio(contentMode: .fill)
.opacity(0.3)
}
VStack(spacing: 12) {
// Header with logo and avatar
HStack {
Image("logo")
.resizable()
.frame(width: 40, height: 40)
Spacer()
Image("avatar")
.resizable()
.frame(width: 32, height: 32)
.clipShape(Circle())
}
// Status icon
Image("statusIcon")
.resizable()
.frame(width: 24, height: 24)
// Chart
Image("chart")
.resizable()
.aspectRatio(contentMode: .fit)
.frame(height: 80)
// Footer with decorative elements
HStack {
Image("star")
.resizable()
.frame(width: 16, height: 16)
Text("Premium")
Image("badge")
.resizable()
.frame(width: 16, height: 16)
}
}
.padding()
}
}
}
Best Practices
Provide All Scales
For the best visual quality, provide all three scale factors:
Optimize Image Sizes
Keep images as small as possible while maintaining quality. Widgets have limited space and memory:
- Use PNG for images with transparency
- Use JPEG for photos and complex images
- Optimize images before adding them to your project
- Consider using vector assets (SF Symbols) when possible
Use Descriptive Names
Name images based on their purpose:
// Good
images: {
profileAvatar: "./avatar.png",
statusIndicator: "./status.png",
chartBackground: "./chart-bg.png"
}
// Avoid
images: {
image1: "./img1.png",
pic: "./pic.png",
asset: "./asset.png"
}
Cache Remote Images
Remote images are downloaded during prebuild and cached in the .expo directory. This ensures:
- Faster subsequent builds
- Offline support
- Consistent images across builds
Fallback for Missing Images
Always handle the case where an image might not load:
if let image = UIImage(named: "avatar") {
Image(uiImage: image)
} else {
Image(systemName: "person.circle.fill")
}
Icon vs Images
Note the difference between the icon config property and images:
icon: The app/extension icon shown on the home screen. Automatically generates all required sizes for iOS.
images: Custom images used within your extension’s UI.
module.exports = {
type: "widget",
// App icon (shown on home screen)
icon: "./assets/app-icon.png",
// Custom images (used in your code)
images: {
logo: "./assets/logo.png",
background: "./assets/bg.png"
}
};
Troubleshooting
Images not showing up
- Make sure you’ve run
expo prebuild after adding images
- Check that the image name is correctly spelled in Swift
- Verify the
Assets.xcassets directory exists in your target folder
- Check the file path is correct and the image file exists
Image quality looks poor
- Ensure you’re providing appropriately sized images for each scale
- Use PNG format for images with sharp edges or transparency
- Avoid upscaling small images - provide native resolution images
Remote images not downloading
- Check your internet connection during prebuild
- Verify the URL is accessible
- Look for error messages in the prebuild output
- Try downloading the image manually to test the URL
Build fails with image errors
- Check that image files aren’t corrupted
- Ensure image formats are supported (PNG, JPEG, SVG)
- Verify file paths don’t contain special characters
- Clear the
.expo cache and try again:
rm -rf .expo
expo prebuild --clean
Image Caching
Images are cached in .expo/widget-icons-{target-name}/ during prebuild. This cache:
- Speeds up subsequent builds
- Allows offline development
- Is automatically invalidated when image sources change
To clear the cache:
rm -rf .expo
expoprebuild --clean
Advanced Usage
Conditional Images
Use a config function to conditionally include images:
module.exports = (config) => {
const isDev = config.extra?.isDev || false;
return {
type: "widget",
images: {
logo: isDev
? "./assets/logo-dev.png"
: "./assets/logo-prod.png",
...(isDev && { debugOverlay: "./assets/debug.png" })
}
};
};
Dynamic Image Sources
Generate image sources programmatically:
const avatarImages = [
"alice",
"bob",
"charlie"
].reduce((acc, name) => {
acc[`avatar_${name}`] = `./assets/avatars/${name}.png`;
return acc;
}, {});
module.exports = {
type: "widget",
images: {
...avatarImages,
logo: "./assets/logo.png"
}
};