The React Native photo & video editor SDK for Expo — customizable, RTL-first, free and MIT-licensed.
→ Full docs & demo at dahab-tech.com/apps/react-native-media-editor
I18nManager reliance).The editor is built for both Android and iOS using native media frameworks for video processing.
| Feature | Android | iOS |
|---|---|---|
| Photo editing | ✅ | ✅ |
| Crop & straighten | ✅ | ✅ |
| Adjustments | ✅ | ✅ |
| Filters | ✅ | ✅ |
| Overlays | ✅ | ✅ |
| Selective focus | ✅ | ✅ |
| Drawing | ✅ | ✅ |
| Text & stickers | ✅ | ✅ |
| Multi-photo editing | ✅ | ✅ |
| Full-resolution photo export | ✅ | ✅ |
| Video trimming | ✅ | ✅ |
| Video cropping | ✅ | ✅ |
| Video cover selection | ✅ | ✅ |
| Video filters & adjustments | ✅ | ✅ |
| Playback speed | ✅ | ✅ |
| Video text/sticker/drawing overlays | ✅ | ✅ |
| Video compression | ✅ | ✅ |
| Video information | ✅ | ✅ |
| Video thumbnails | ✅ | ✅ |
The package provides a React Native / Expo API while keeping media processing in the native platform layer.
React Native / Expo
│
▼
@dahab-tech/react-native-media-editor
│
├── Photo Editor
│
└── Video Editor
│
┌───────┴────────┐
▼ ▼
Android iOS
│ │
▼ ▼
Media3 Transformer AVFoundation
Video processing is performed by the native media frameworks rather than by JavaScript.
The package does not use FFmpeg for video processing.
This architecture allows the editor to use the media capabilities provided by each platform while keeping the React Native API consistent across Android and iOS.
npx expo install @dahab-tech/react-native-media-editor
Autolinking picks up the native module, and expo install auto-adds the SDK's config plugin to your app.json (only matters for VideoEditor on Android — see below).
Requires a development build (Expo Go is not supported because the package contains native code):
npx expo run:ios # or run:android
The editor uses native modules and therefore requires a development build. It is not supported in Expo Go.
react-native-worklets@shopify/react-native-skiareact-native-gesture-handlerreact-native-safe-area-contextAdditional dependencies are required depending on which editor is used. See the installation section above for the complete peer dependency list.
The Android VideoEditor requires:
minSdkVersion 26+The Expo config plugin automatically raises the Android minimum SDK when required.
For bare React Native projects, make sure the Android project uses:
android.minSdkVersion=26
The VideoEditor additionally uses:
expo-videoThe functional video APIs such as getVideoInfo, getVideoThumbnail, and trimVideo can be used without mounting the VideoEditor component itself.
Only install what you use:
| Feature | Extra dependencies |
|---|---|
PhotoEditor component |
npx expo install @shopify/react-native-skia react-native-reanimated react-native-worklets react-native-gesture-handler react-native-safe-area-context @expo/vector-icons expo-font |
VideoEditor component |
npx expo install @shopify/react-native-skia expo-video react-native-reanimated react-native-worklets react-native-gesture-handler react-native-safe-area-context @expo/vector-icons expo-font |
Functional video API (trimVideo, getVideoThumbnail, getVideoInfo) |
none |
Both editors need @shopify/react-native-skia (the video editor renders its effects preview and overlays with Skia too) and react-native-reanimated v4+ with react-native-worklets. On Android, VideoEditor requires minSdkVersion 26+ — Skia's video decoder throws below 26 on every device. The SDK's config plugin raises it for you; npx expo install adds it automatically, or add it yourself:
{
"expo": {
"plugins": ["@dahab-tech/react-native-media-editor"]
}
}
The plugin is raise-only — it never lowers a minSdkVersion you've set higher — and photo-only apps can skip it. Bare React Native projects set android.minSdkVersion=26 in android/gradle.properties manually. The default Expo template already ships most peers, so usually only @shopify/react-native-skia and/or expo-video are new. A Metro Unable to resolve module <package> error means that peer is missing: install it and rebuild your development build.
import { VideoEditor } from '@dahab-tech/react-native-media-editor/videoEditor';
<VideoEditor
source={videoUri}
onCancel={() => {}}
onExport={(result) => console.log(result.uri, result.durationMs)}
// Fires when the user picks a cover; defaults to the exported video's first frame otherwise.
onCoverSelected={(cover) => console.log(cover.uri)}
onError={(error) => console.warn(error)}
/>
import {
getVideoInfo,
getVideoThumbnail,
trimVideo,
} from '@dahab-tech/react-native-media-editor';
const info = await getVideoInfo(videoUri);
// { durationMs, width, height, rotation, fps } — display dimensions, rotation applied
const trimmed = await trimVideo(videoUri, { startMs: 1000, endMs: 6000 });
// { uri, durationMs } — lossless passthrough when the source allows it
const cropped = await trimVideo(videoUri, {
startMs: 1000,
endMs: 6000,
crop: { x: 100, y: 50, width: 640, height: 360 }, // display-space pixels; forces re-encode
});
const compressed = await trimVideo(videoUri, {
startMs: 0,
endMs: 30_000,
compression: { preset: 'medium', maxDimension: 1080 }, // presence forces re-encode
});
// `compression.preset` = 'high' | 'medium' | 'low' (bpp/frame heuristic on OUTPUT dims)
// `compression.maxDimension` caps the long edge post-crop; never upscales
// `compression.bitrateMbps` overrides `preset` when you need an exact target
const cover = await getVideoThumbnail(videoUri, {
timeMs: 2500,
quality: 0.9,
maxWidth: 720,
});
// { uri, width, height }
Video operations are performed natively using the media framework provided by the operating system.
Not every video operation requires the same type of processing.
| Operation | Processing |
|---|---|
| Trim | Lossless/pass-through when supported by the requested operation |
| Crop | Re-encoding required |
| Compression | Re-encoding required |
| Filters | Re-encoding required |
| Adjustments | Re-encoding required |
| Text overlays | Re-encoding required |
| Sticker overlays | Re-encoding required |
| Drawing overlays | Re-encoding required |
| Playback speed | Re-encoding required |
A trim operation can preserve the original encoded media when the requested operation allows it. Operations that modify video pixels, such as cropping, filters, adjustments, or burned-in overlays, require the video to be processed and re-encoded.
The implementation uses the native media stack on each platform:
No FFmpeg runtime is required by the editor.
Because encoding is performed by the native platform, the codecs and export capabilities available to an individual device or operating-system version can affect the final output.
The ability to read or play a media format does not necessarily mean that the same format can be exported.
Input support and export support depend on the native media framework and the codecs available on the target platform.
For this reason, applications should treat the exported file format and codec as an implementation detail of the native export pipeline rather than assuming that every input format can be exported unchanged.
The package also exposes functional APIs for working with videos without rendering the full VideoEditor UI.
| API | Description | Re-encoding |
|---|---|---|
getVideoInfo() |
Reads metadata and information about a video | No |
getVideoThumbnail() |
Generates a thumbnail from a video at a requested timestamp | No |
trimVideo() |
Trims a video and can optionally crop or compress it | Depends on options |
getVideoInfo()Reads information about a video, such as its dimensions, duration, and media metadata.
getVideoThumbnail()Generates a thumbnail from a selected point in the video.
The API supports options such as:
maxDimension)trimVideo()Trims a video and can optionally apply:
A basic trim can use the native pass-through/lossless path when possible.
Crop and compression require re-encoding because the video data itself must be modified.
import { PhotoEditor } from '@dahab-tech/react-native-media-editor/photoEditor';
<PhotoEditor
source={photoUri}
onCancel={() => {}}
onExport={(result) => console.log(result.width, result.height)} // result.base64 = JPEG
exportQuality={90}
/>
Built-in tools: Crop (free/1:1/4:3/16:9/9:16 aspect presets, straighten dial, 90° rotate, flip), Adjust (15 parameters including brightness, contrast, saturation, exposure, highlights/shadows, temperature/tint, sharpen, structure, vignette), Filters (64 built-in presets with live previews), Overlays (Light + Mood packs), Focus (radial + linear tilt-shift), Draw (4 brushes), Text (draggable overlays with color, font, alignment, and per-line pill backgrounds), and Stickers/emoji. Multi-photo batch mode is supported. Export renders at the image's intrinsic resolution, not the screen size.
English and Arabic ship built in. Pass locale to switch, or provide your own strings:
<VideoEditor source={uri} locale="ar" />
<VideoEditor
source={uri}
locale="fr"
strings={{ cancel: 'Annuler', export: 'Exporter' /* ... */ }}
direction="auto" // 'ltr' | 'rtl' | 'auto' (default: from locale, then I18nManager)
/>
Layout direction is resolved per editor: explicit direction prop → locale script → I18nManager. The trim timeline intentionally stays LTR in RTL locales, matching platform media conventions.
<VideoEditor
source={uri}
theme={{ colors: { accent: '#FF375F', background: '#000000' } }}
/>
Any subset of colors, spacing, and radius can be overridden; the rest falls back to the default dark theme.
Edited media and generated thumbnails are written to:
caches/MediaEditor/
These files are stored in the application's cache directory.
If an exported video or photo needs to be retained permanently or shared outside the application's temporary workflow, copy or move the resulting file to an appropriate persistent location.
Applications should not assume that files in the cache directory will remain available indefinitely, because the operating system may remove cached data when storage needs to be reclaimed.
The following are important implementation details to keep in mind when integrating the editor:
minSdkVersion 26+.See example/ for a working demo with a video picker, photo picker, and an English/Arabic RTL toggle.
MIT © DahabTech LLC