@danielxceron/youtube-transcript
v2.0.0
Published
Fetch transcript from a youtube video
Maintainers
Readme
youtube-transcript
About This Fork
This is a fork of the original youtube-transcript package with a resilient extraction pipeline. It tries YouTube's InnerTube API first and falls back to HTML scraping when needed, so a single blocked route does not break transcript retrieval.
What's New
- InnerTube first: signed caption URLs from the Android and iOS clients, rotated automatically
- HTML scraping fallback when InnerTube returns no captions
- Reusable clients with timeouts, retries, concurrency limits and caching
- Batch fetching with per-video results
- Robust parsers for srv1, srv3 and json3 caption formats
- Complete HTML entity decoding including numeric and double-encoded entities
See CHANGELOG.md for the full history.
Upgrading from 1.x
Version 2.0.0 changes observable behavior. The static fetchTranscript API keeps working, but these points may require adjustments:
- Manual captions are preferred over automatic ones when no
langis given. There is no flag to restore the old ASR-first preference. segment.langis the selected track's language (de-DE) instead of the requested one (de). Compare with prefixes if needed:const wanted = (segment) => segment.lang?.toLowerCase().startsWith('de');- ESM entry point renamed from
dist/youtube-transcript.esm.jstodist/youtube-transcript.mjs. Import through the package name or update direct CDN paths. - Caching and retries are enabled (
5 min,2 retries,20 stimeout). To mimic 1.x latency and freshness:const youtube = YoutubeTranscript.create({ cache: false, retries: 0 }); - New error classes:
YoutubeTranscriptNetworkErrorandYoutubeTranscriptAbortErrornow surface transport and abort failures.
Installation
$ npm i @danielxceron/youtube-transcriptUsage
import { YoutubeTranscript } from '@danielxceron/youtube-transcript';
const transcript = await YoutubeTranscript.fetchTranscript('videoId or URL');
console.log(transcript);Each segment has the shape { text, duration, offset, lang }, where duration and offset are in seconds.
Reusable client
For servers and long-running processes, create one client and share it:
import { YoutubeTranscript } from '@danielxceron/youtube-transcript';
const youtube = YoutubeTranscript.create({
timeoutMs: 15000,
retries: 2,
concurrency: 8,
cache: { ttlMs: 300000, maxEntries: 500 },
});
const transcript = await youtube.fetchTranscript('videoId', { lang: 'en' });Batch fetching
const results = await youtube.fetchTranscripts(['videoIdA', 'videoIdB', 'videoIdC']);
for (const { videoId, transcript, error } of results) {
if (error) console.error(videoId, error.message);
else console.log(videoId, transcript.length);
}Duplicate identifiers are fetched only once.
Options
| Option | Default | Description |
| -------------- | ------------ | ---------------------------------------------------- |
| fetch | global fetch | Custom fetch implementation (proxies, undici, tests) |
| timeoutMs | 20000 | Timeout per HTTP request |
| retries | 2 | Retries for network errors, 429 and 5xx |
| concurrency | 6 | Maximum in-flight requests per client |
| cache | enabled | false disables it, or { ttlMs, maxEntries } |
| clients | Android→iOS | Custom InnerTube clients |
| apiKey | public key | InnerTube API key |
| userAgent | Chrome | User agent used by the HTML strategy |
| htmlFallback | true | Disable to skip HTML scraping |
Per-call options: lang, timeoutMs, retries, signal, cookie.
Supported URL Formats
- Standard videos:
https://www.youtube.com/watch?v=VIDEO_ID - Short URLs:
https://youtu.be/VIDEO_ID - YouTube Shorts:
https://www.youtube.com/shorts/VIDEO_ID - Live:
https://www.youtube.com/live/VIDEO_ID - Embedded videos:
https://www.youtube.com/embed/VIDEO_ID - Direct video IDs:
VIDEO_ID
Language matching uses regional prefixes, so lang: 'en' accepts en, en-US and en-GB. Manual captions are preferred over automatic ones.
Error Handling
YoutubeTranscriptTooManyRequestError: rate limiting detectedYoutubeTranscriptVideoUnavailableError: video not accessibleYoutubeTranscriptDisabledError: transcripts disabled for videoYoutubeTranscriptNotAvailableError: no transcripts availableYoutubeTranscriptNotAvailableLanguageError: requested language not availableYoutubeTranscriptEmptyError: every extraction strategy returned nothingYoutubeTranscriptNetworkError: transport failure after retriesYoutubeTranscriptAbortError: request aborted throughsignal
Every error exposes name, videoId (when applicable) and cause (when wrapped).
Development
Requires Node.js 20 or newer (.nvmrc pins 22).
$ npm ci
$ npm run check # format:check + typecheck + tests + build
$ npm test # unit tests, mocked network
$ npm run typecheck
$ npm run format
$ npm run build
$ npm run smoke -- dQw4w9WgXcQRun a single file or watch mode:
$ npx vitest run tests/transcript.test.ts
$ npm run test:watchLive tests hit YouTube for real and are opt-in:
$ YOUTUBE_TRANSCRIPT_LIVE=1 npx vitest run tests/live.test.tsCI covers Node 20, 22 and 24; a scheduled workflow runs the live suite weekly. Releases are published by GitHub Actions when a v* tag is pushed, using the NPM_TOKEN secret and npm provenance.
Environment Compatibility
| Strategy | Client-Side | Local Server | Production Server | | ------------- | ----------- | ------------ | ----------------- | | InnerTube API | ✅ | ✅ | ✅ | | HTML Scraping | ✅ | ✅ | ✅ |
License
MIT © 2026 Daniel Cerón Claros
Based on youtube-transcript by Kakulukian.
