expo-ytdlp
v1.0.1
Published
A native Android Expo module providing a TypeScript API around yt-dlp.
Maintainers
Readme
expo-ytdlp
A native Android Expo module providing a modern TypeScript API around yt-dlp. It embeds the Python runtime, yt-dlp and all site extractors through the yt-dlp-android library — no Python, yt-dlp, Chaquopy, FFmpeg or Termux setup is required in your app.
import YtDlp from 'expo-ytdlp';
const info = await YtDlp.extractInfo('https://www.youtube.com/watch?v=...');
console.log(info.title);
console.log(info.formats);
const task = await YtDlp.download({
url: 'https://www.youtube.com/watch?v=...',
format: 'bestvideo+bestaudio',
output: { directory: 'Movies' },
});
task.addListener('progress', (progress) => {
console.log(progress.percent);
});
await task.cancel();Requirements
Platform: Android only. Importing on iOS/web throws
YtDlpErrorwith codeUNSUPPORTED_PLATFORM.Expo SDK: 57 (tested against Expo 57 / React Native 0.86).
Minimum Android: API 24.
Native build required. This is a custom native module. It does not work inside standard Expo Go — Expo Go cannot load arbitrary custom native modules. Use a development build:
npx expo prebuild npx expo run:android
Installation
npx expo install expo-ytdlpIf expo install does not resolve the package (e.g. before it is indexed),
fall back to:
npm install expo-ytdlpUsage
Extract media information
import YtDlp from 'expo-ytdlp';
const info = await YtDlp.extractInfo(url);
console.log(info.title); // string | undefined
console.log(info.duration);
console.log(info.thumbnail);
console.log(info.formats);extractInfo never downloads media. Fields that a site does not provide are
undefined — no field is guaranteed for every site.
List formats
const formats = await YtDlp.getFormats(url);getFormats reuses the extraction result, so it does not extract twice.
Download
const task = await YtDlp.download({
url,
format: 'bestvideo+bestaudio', // raw yt-dlp format expression
output: {
directory: 'Movies',
filename: '%(title)s.%(ext)s',
},
});
task.addListener('progress', (progress) => {
console.log(progress.percent, progress.speedBytesPerSecond, progress.etaSeconds);
});
task.addListener('completed', (result) => {
console.log(result.path);
});
task.addListener('error', (error) => {
console.log(error.code, error.message);
});download resolves as soon as the task is registered; progress and the final
result arrive through the task's listeners. Multiple downloads can run at the
same time — every event carries a taskId.
Cancel
await task.cancel();Cancellation calls the native cancellation path and aborts the underlying yt-dlp download. To cancel a task after your JS task object is gone:
await YtDlp.cancel(taskId);Version
const version = await YtDlp.getVersion();
// { ytDlp: '2026.xx.xx', library: '0.1.0' }The embedded yt-dlp version and the npm package version are independent.
Supported download options
| Option | Description |
| --- | --- |
| url | Required. Any URL supported by yt-dlp. |
| format | Raw yt-dlp format expression, e.g. best, bestaudio, best[height<=720]. |
| output.directory | Subdirectory under the app's yt-dlp folder. Sanitized. |
| output.filename | yt-dlp output template, e.g. %(title)s.%(ext)s. Sanitized. |
| headers | Extra HTTP headers, e.g. { Referer: '...' }. |
| userAgent | Custom User-Agent. |
| referer | Custom Referer. |
| proxy | Proxy URL. |
| cookies.path | Path to a Netscape-format cookies file. |
| playlist.enabled | Default false — a playlist URL downloads only the first item unless enabled. |
| playlist.start / playlist.end | Playlist item range (1-based). |
| subtitles.enabled | Write subtitles. |
| subtitles.languages | e.g. ['en', 'bn']. |
| subtitles.autoGenerated | Also write auto-generated subtitles. |
| network.timeout | Socket timeout in seconds. |
| network.retries | Number of retries. |
Not supported (yet)
The bundled yt-dlp-android build ships no FFmpeg, so the following are
rejected up front with PROCESSING_FAILED rather than silently ignored:
merge: true(e.g.bestvideo+bestaudiowill fail because merging requires FFmpeg)audio.*(audio extraction / re-encoding)metadata(embedding)thumbnail(embedding)
Use format: 'best' or format: 'bestaudio' and download a single stream
that requires no post-processing.
Download task
A DownloadTask exposes:
id: stringcancel(): Promise<void>getStatus(): Promise<DownloadStatus>getProgress(): Promise<DownloadProgress | null>addListener(event, listener): Subscription
Statuses: queued | extracting | downloading | processing | completed |
cancelled | failed.
Events:
progress→DownloadProgress(percent,downloadedBytes,totalBytes,speedBytesPerSecond,etaSeconds,filename,phase)state→{ taskId, status }completed→DownloadResult(taskId,path,filename,size)error→YtDlpError
Progress events are throttled to ~200 ms and numeric fields are undefined
when the value is unknown (never NaN).
Errors
All failures normalize to YtDlpError with a code:
INVALID_URL, EXTRACTION_FAILED, DOWNLOAD_FAILED, CANCELLED,
FORMAT_UNAVAILABLE, NETWORK_ERROR, AUTHENTICATION_REQUIRED,
GEO_RESTRICTED, PRIVATE_CONTENT, AGE_RESTRICTED, PROCESSING_FAILED,
STORAGE_ERROR, INIT_FAILED, UNSUPPORTED_PLATFORM, UNKNOWN.
import { YtDlpError } from 'expo-ytdlp';
try {
await YtDlp.extractInfo(url);
} catch (error) {
if (error instanceof YtDlpError) {
console.log(error.code, error.message);
}
}Raw native stack traces are never surfaced to the user.
Storage
Files are written to app-specific external storage:
Android/data/<your-package>/files/yt-dlp/<output.directory>/...Filenames and directory segments are sanitized against illegal characters,
path traversal, excessive length and empty names. This avoids dangerous
permissions like MANAGE_EXTERNAL_STORAGE. The returned DownloadResult.path
is an absolute path inside your app's own storage.
Limitations
- Android only.
- No FFmpeg-based post-processing (see "Not supported" above).
- No background/foreground service: downloads pause if the JS/native runtime is destroyed. Persisting task IDs lets you re-issue cancellation later.
- yt-dlp site support changes frequently. Not every website works forever, and not every site provides every field.
- This package does not bundle or provide a way to update the embedded yt-dlp at runtime.
- Playlists are opt-in via
playlist.enabledto avoid accidental bulk downloads.
Legal & responsible use
expo-ytdlp is a technical wrapper around yt-dlp. It does not circumvent
DRM (Widevine, FairPlay, PlayReady, ...), bypass authentication, or access
private or unauthorized content — content that requires DRM or authentication
will fail with an error.
You are responsible for complying with:
- website terms of service
- copyright law and content licenses
- authentication rules
- platform policies
Do not use this library to download content you do not have the right to download.
Third-party licenses
| Component | License |
| --- | --- |
| expo-ytdlp (this package) | MIT |
| yt-dlp-android (Maven dev.ffmpegkit-maintained:yt-dlp-android) | MIT |
| yt-dlp | Unlicense |
| Chaquopy | BSD-style (per the embedded distribution) |
Re-verify third-party licenses at release time.
