react-native-set-live-wallpaper
v1.0.0
Published
Set static and live wallpapers from React Native and Expo apps
Maintainers
Readme
react-native-set-live-wallpaper
Set static images and video wallpapers from an Expo or React Native app. Android opens the native wallpaper workflow; iOS exports media to Photos so the user completes the wallpaper action in Apple's supported UI.
Requires a development or production build. Expo Go cannot load this custom native module.
Features
- Static wallpapers from local images and remote HTTP(S) URLs.
- Android native Home screen, Lock screen, and Both destination flow.
- Android live MP4 wallpaper with drag, pinch, and zoom positioning.
- Themeable Android library previews.
- An iOS Photos album named after the host app.
- iOS MP4-to-Live-Photo conversion for Lock Screen motion on iOS 17+.
- Cleanup for temporary files and library-owned iOS Photos assets.
- Expo config plugin for Android wallpaper declarations and iOS Photos permissions.
Installation
Install with pnpm:
pnpm add react-native-set-live-wallpaperThis package uses Expo Modules. Rebuild your native app after installing:
npx expo prebuild
pnpm ios
# or
pnpm androidFor a dynamic Expo config, add the config plugin explicitly:
// app.config.ts
export default {
expo: {
plugins: ['react-native-set-live-wallpaper'],
},
};The plugin adds Android's wallpaper service and SET_WALLPAPER permission, plus the iOS Photos usage descriptions. Rebuild after adding or upgrading the package.
Quick start
import { setLiveWallpaperFromUrl, setWallpaperFromFile } from 'react-native-set-live-wallpaper';
const staticResult = await setWallpaperFromFile({
source: 'file:///path/to/photo.jpg',
target: 'both',
});
const liveResult = await setLiveWallpaperFromUrl({
source: 'https://example.com/wallpaper.mp4',
muted: true,
loop: true,
iosLivePhotoDuration: 2,
});These methods open a native user flow and normally return requires-user-action. This means the system or Photos workflow is ready; it does not mean the wallpaper has already been applied.
How it works
| Platform | Static image | Video / live wallpaper | | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Android | Stages the image privately, then opens Android's native crop-and-set screen by default. The device controls the final Home / Lock / Both choices. | Stores one private active MP4 for the live-wallpaper service, opens a positioning preview by default, then opens Android's live-wallpaper confirmation UI. | | iOS | Saves the image in a Photos album named after the host app. The user opens it in Photos and chooses Share → Use as Wallpaper. | Opens a native drag-and-zoom preview, bakes that crop into a paired Live Photo, and saves it in the app album for manual Lock Screen setup on iOS 17+. | | Web | Not supported. | Not supported. |
Apple provides no public API for an app to apply a wallpaper directly. iOS always needs the user to finish in Photos or Settings.
Android behavior
previewMode: 'system'is the default for static images. It prefers the device's current wallpaper preview and uses Android's legacy cropper only as a compatibility fallback.previewMode: 'library'uses this package's themeable preview and reliable Home / Lock / Both picker.- One private MP4 remains while it is the active live wallpaper because the Android wallpaper service must play it after the app closes. It is replaced when another video is chosen and is never added to Gallery.
- Temporary static-image and download-staging files are removed after the flow finishes.
iOS behavior
- The module creates or reuses a regular Photos album named from
CFBundleDisplayName, the host app's display name. - It requests Photos read/write access to organize the album and support cleanup.
- Every live-video call opens a full-screen native preview. Drag to position the video, pinch to zoom, then tap Continue. The exported video and Live Photo poster use the selected crop.
- Remote iOS videos stream into the preview immediately while the complete conversion file downloads in the background. A loading indicator is shown until playback is ready.
- Local iOS videos support regular
file://paths plus Photos-libraryph://andassets-library://identifiers. - Live Photo conversion uses the complete video by default. Set
iosLivePhotoDurationto a positive number to convert only that many seconds from the beginning. - Apple does not publish a custom Live Photo wallpaper duration limit. For the most reliable Lock Screen motion, use a short one-to-two-second value; longer exports may remain playable in Photos but be rejected for animated wallpaper.
- Call
deleteSavedWallpaperAsset()only after the user has finished setting the wallpaper. - The iOS Simulator can show a black wallpaper canvas for imported Photos. Test the final wallpaper flow on a physical iPhone.
API
Import the functions you need from the package:
import {
WallpaperError,
clearCachedFiles,
deleteSavedWallpaperAsset,
getPlatformCapabilities,
isSupported,
setLiveWallpaperFromFile,
setLiveWallpaperFromUrl,
setWallpaperFromFile,
setWallpaperFromUrl,
} from 'react-native-set-live-wallpaper';setWallpaperFromFile(options)
Prepares a local static image.
const result = await setWallpaperFromFile({
source: localImageUri,
target: 'both',
previewMode: 'system',
});Use a file:// URI on iOS. Android accepts file:// and content:// URIs.
setWallpaperFromUrl(options)
Downloads and prepares a static image from an HTTP(S) URL.
const result = await setWallpaperFromUrl({
source: 'https://cdn.example.com/wallpaper.jpg',
target: 'home',
});setLiveWallpaperFromFile(options)
Prepares a local MP4 file as a live wallpaper.
const result = await setLiveWallpaperFromFile({
source: localVideoUri,
muted: true,
iosLivePhotoDuration: 2,
showSystemPreview: true,
});setLiveWallpaperFromUrl(options)
Downloads an MP4 and prepares it as a live wallpaper.
const result = await setLiveWallpaperFromUrl({
source: 'https://cdn.example.com/wallpaper.mp4',
muted: true,
loop: true,
iosLivePhotoDuration: 2,
posterImage: 'https://cdn.example.com/poster.jpg',
});getPlatformCapabilities()
Returns the feature support reported by the installed native platform.
const capabilities = await getPlatformCapabilities();
// {
// platform: 'android' | 'ios',
// staticWallpaper: boolean,
// lockScreen: boolean,
// liveVideo: boolean,
// remoteSource: boolean,
// crop: boolean,
// requiresUserAction: true,
// implementationPhase: number,
// }Use this before showing live-wallpaper UI. On iOS, liveVideo is true only on iOS 17 or later.
isSupported(feature)
Checks one named feature:
const supportsVideo = await isSupported('live-video');Accepted feature names: 'static-home', 'static-lock', 'live-video', 'remote-source', and 'crop'.
clearCachedFiles()
Deletes temporary download and conversion files created by the module.
await clearCachedFiles();On Android, this does not remove the active private MP4 currently used by the live-wallpaper service.
deleteSavedWallpaperAsset(assetId)
Deletes an iOS Photos asset that this library previously created.
const result = await setWallpaperFromFile({ source: localImageUri });
if (result.platform === 'ios' && result.status === 'requires-user-action' && result.assetId) {
// Wait until the user has finished setting the wallpaper in Photos.
await deleteSavedWallpaperAsset(result.assetId);
}It resolves to true when the library-owned asset was deleted and false when the identifier is unknown or no longer exists. It is a no-op on Android and web.
Options
Static wallpaper options
| Option | Type | Default | Description |
| ------------------ | -------------------------- | ---------- | ----------------------------------------------------------------------------------------------------------------------------------------- |
| source | string | required | Local URI for setWallpaperFromFile, or HTTP(S) URL for setWallpaperFromUrl. |
| target | 'home' / 'lock' / 'both' | 'both' | Destination for Android's library preview. The Android system preview owns its own selector; iOS ignores it. |
| crop | CropConfiguration | — | Validated crop data for callers that persist their own position. Current native previews position interactively; iOS uses Apple's editor. |
| allowBackup | boolean | true | Android library-preview only. Passes Android's wallpaper backup preference to WallpaperManager. |
| previewMode | 'system' / 'library' | 'system' | Android only. system uses the OS screen; library uses this package's themeable image preview. |
| showTargetPicker | boolean | true | Android library-preview only. Shows Home / Lock / Both after positioning. |
| previewStyle | WallpaperPreviewStyle | — | Android library-preview only. Colors and labels for the package preview. |
Live wallpaper options
| Option | Type | Default | Description |
| ---------------------- | ----------------------- | ---------- | ----------------------------------------------------------------------------------------------------------------------------------------------- |
| source | string | required | Local MP4 URI (file://, iOS ph://, or assets-library://) for setLiveWallpaperFromFile, or HTTP(S) MP4 URL for setLiveWallpaperFromUrl. |
| crop | CropConfiguration | — | Initial normalized position and zoom for the native preview. The user's final interactive crop is baked into the Android wallpaper or iOS Live Photo. |
| muted | boolean | true | Mutes Android playback and removes audio when iOS converts the MP4 to a Live Photo. |
| loop | boolean | true | Android only. Controls whether the live wallpaper repeats. iOS controls Live Photo playback. |
| iosLivePhotoDuration | number | full video | iOS only. Converts this many seconds from the beginning. Values longer than the source are capped to the source duration. |
| showSystemPreview | boolean | true | Android only. true opens the package positioning preview before Android confirmation; false opens Android's general live-wallpaper chooser. |
| previewStyle | WallpaperPreviewStyle | — | Colors and labels for the Android and iOS native video positioning previews. |
| posterImage | string | — | iOS only. Local file or HTTP(S) image used as the Live Photo key image. When omitted, a frame from the video is used. |
CropConfiguration
type CropConfiguration = {
mode: 'cover' | 'contain' | 'stretch';
position?: { x: number; y: number };
scale?: number;
};position.x and position.y must be between 0 and 1; scale must be greater than 0.
WallpaperPreviewStyle
Used by Android's package-provided previews and the iOS live-video crop preview. Static iOS exports do not use it.
The preview is a native full-screen controller, so it cannot render React components or accept a React Native StyleSheet object. The nested options below control each native section. Numeric sizes use points on iOS and density-independent pixels on Android. Colors accept #RRGGBB or #AARRGGBB.
| Option | Type | Description |
| ---------------- | --------------------------------- | -------------------------------------------------------------- |
| backgroundColor | string | Color behind the image or video. |
| statusBar | 'hidden' / 'light' / 'dark' | Hides the status bar or selects light/dark status-bar content. Defaults to light. |
| instruction | WallpaperPreviewInstructionStyle | Drag/pinch instruction chip. |
| controls | WallpaperPreviewControlsStyle | Container holding Cancel and Continue. |
| cancelButton | WallpaperPreviewButtonStyle | Cancel button appearance and label. |
| continueButton | WallpaperPreviewButtonStyle | Continue button appearance and label. |
| loading | WallpaperPreviewLoadingStyle | Video loading indicator, message, and error message. |
Shared instruction, button, and loading appearance options:
| Option | Type | Description |
| ------------------- | ----------------------------------------- | ------------------------------------------------------------------ |
| visible | boolean | Shows or hides the element. |
| backgroundColor | string | Element background color. |
| textColor | string | Label color. |
| fontSize | number | Label size. |
| fontWeight | 'regular' / 'medium' / 'semibold' / 'bold' | Label weight. |
| fontFamily | string | Installed native font/PostScript family name. |
| textAlign | 'left' / 'center' / 'right' | Label alignment. |
| cornerRadius | number | Rounded-corner radius. |
| borderColor | string | Border color. |
| borderWidth | number | Border width. |
| paddingHorizontal | number | Left and right content padding. |
| paddingVertical | number | Top and bottom content padding. |
instruction also supports text, position: 'top' | 'center' | 'bottom', marginHorizontal, marginVertical, and optional height. cancelButton and continueButton support text and height.
controls options:
| Option | Type | Description |
| ------------------- | ------------------------------------- | --------------------------------------------------------------------------- |
| backgroundColor | string | Controls-container color. |
| position | 'top' / 'bottom' | Places the controls container at the top or bottom. |
| direction | 'horizontal' / 'vertical' | Arranges the two buttons in a row or column. |
| blurEffect | 'none' / 'light' / 'dark' / 'system' | iOS background blur. Android uses backgroundColor. |
| cornerRadius | number | Container corner radius. |
| borderColor | string | Container border color. |
| borderWidth | number | Container border width. |
| paddingHorizontal | number | Internal horizontal padding. |
| paddingVertical | number | Internal vertical padding. |
| marginHorizontal | number | Space between the container and screen sides. |
| marginVertical | number | Space between the container and its selected screen edge. |
| gap | number | Space between Cancel and Continue. |
loading also supports text, errorText, indicatorColor, marginHorizontal, and marginVertical.
import type { WallpaperPreviewStyle } from 'react-native-set-live-wallpaper';
const previewStyle = {
backgroundColor: '#050706',
statusBar: 'hidden',
instruction: {
text: 'Drag to position · Pinch to zoom',
position: 'top',
backgroundColor: '#B319211C',
textColor: '#FFFFFF',
fontSize: 15,
fontWeight: 'semibold',
cornerRadius: 18,
paddingHorizontal: 16,
paddingVertical: 9,
marginHorizontal: 24,
marginVertical: 16,
},
controls: {
position: 'bottom',
direction: 'horizontal',
backgroundColor: '#CC050706',
blurEffect: 'system',
cornerRadius: 24,
paddingHorizontal: 12,
paddingVertical: 12,
marginHorizontal: 16,
marginVertical: 16,
gap: 12,
},
cancelButton: {
text: 'Cancel',
backgroundColor: '#1FFFFFFF',
textColor: '#FFFFFF',
cornerRadius: 16,
fontWeight: 'bold',
height: 52,
},
continueButton: {
text: 'Choose screen',
backgroundColor: '#D9FF69',
textColor: '#172000',
cornerRadius: 16,
fontWeight: 'bold',
height: 52,
},
loading: {
text: 'Preparing preview…',
errorText: 'This video could not be loaded',
textColor: '#FFFFFF',
indicatorColor: '#D9FF69',
},
} satisfies WallpaperPreviewStyle;The earlier flat keys (controlsBackgroundColor, instructionBackgroundColor, accentColor, accentTextColor, textColor, instructionText, cancelText, and continueText) remain supported for backward compatibility, but the nested API is recommended.
Results and errors
WallpaperResult
| Result | Meaning |
| ----------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------- |
| { status: 'set', platform: 'android' } | Reserved direct-set result type. Current Android flows normally return requires-user-action because they open system confirmation UI. |
| { status: 'requires-user-action', platform, instructions?, assetId? } | The native wallpaper flow or iOS Photos export is ready. assetId is returned for iOS-created Photos assets. |
| { status: 'cancelled', platform } | The user cancelled a package-controlled flow. |
Failures reject with WallpaperError, which contains a code and message:
try {
await setWallpaperFromUrl({ source: imageUrl });
} catch (error) {
if (error instanceof WallpaperError) {
console.log(error.code, error.message);
}
}| Error code | Typical cause |
| ----------------------- | ------------------------------------------------------------------------------------------- |
| E_INVALID_SOURCE | Empty URI or the wrong URI scheme for the selected function. |
| E_INVALID_CROP | Invalid crop position or scale. |
| E_NETWORK | A remote source could not be downloaded. |
| E_DECODING | The image or video could not be read or converted. |
| E_PERMISSION_DENIED | Photos access was denied on iOS. |
| E_UNSUPPORTED_FEATURE | The platform, OS version, or required system handler does not support the requested action. |
Source URI rules
| Function family | Allowed source |
| ---------------------- | ------------------------- |
| *FromUrl | http:// or https:// |
| *FromFile on Android | file:// or content:// |
| *FromFile on iOS | file:// |
For Expo ImagePicker, use its returned asset.uri:
import * as ImagePicker from 'expo-image-picker';
import { setWallpaperFromFile } from 'react-native-set-live-wallpaper';
const picked = await ImagePicker.launchImageLibraryAsync({
mediaTypes: ['images'],
quality: 1,
});
if (!picked.canceled) {
await setWallpaperFromFile({ source: picked.assets[0].uri });
}Example app
This repository includes a complete Expo example with local and remote static-image and MP4 actions:
pnpm install
pnpm build
pnpm --dir example ios
# or
pnpm --dir example androidThe bundled image is “Mountains Landscape” on Wikimedia Commons, released under CC0. The remote example video is Mixkit's “Waterfall Between Green Hills” under the Mixkit Stock Video Free License.
Requirements
- Expo SDK 57 or a compatible React Native app with Expo Modules.
- A development or production build.
- Android device with a wallpaper provider.
- iOS 16.4+ for static image export; iOS 17+ for MP4-to-Live-Photo conversion.
License
MIT © 2026 Aziz Falah Hassan (Aziz-AXG)
