Security News
Research
Supply Chain Attack on Rspack npm Packages Injects Cryptojacking Malware
A supply chain attack on Rspack's npm packages injected cryptomining malware, potentially impacting thousands of developers.
youtubei.js
Advanced tools
A full-featured library that allows you to get detailed info about any video, subscribe, unsubscribe, like, dislike, comment, search, download videos/music and much more!
A full-featured wrapper around the Innertube API, which is what YouTube itself uses.
Innertube is an API used across all YouTube clients, it was created to simplify the internal structure of the platform in a way that updates, tweaks, and experiments can be easily made. This library handles all the low-level communication with Innertube, providing a simple and efficient way to interact with YouTube programmatically.
And huge thanks to @gatecrasher777 for his research on the workings of the Innertube API!
~ And more!
Do note that you must be signed in to perform actions that involve an account; such as commenting, liking/disliking videos, sending messages to a live chat, etc.
NodeJS v14 or greater
To verify things are set up properly, you can run this:
node --version
npm install youtubei.js@latest
yarn add youtubei.js@latest
npm install git+https://github.com/LuanRT/YouTube.js.git
Create an Innertube instance (or session):
const Innertube = require('youtubei.js');
const youtube = await new Innertube({ gl: 'US' }); // all parameters are optional.
Client: YOUTUBE
| YTMUSIC
const search = await youtube.search('Looking for life on Mars - Documentary', { client: 'YOUTUBE' });
{
query: string,
corrected_query: string,
estimated_results: number,
videos: [
{
id: string,
url: string,
title: string,
description: string,
metadata:{
view_count: string,
short_view_count_text: {
simple_text: string,
accessibility_label: string
},
thumbnails: [Array],
duration: {
seconds: number,
simple_text: string,
accessibility_label: string
},
published: string,
badges:[Array],
owner_badges:[Array]
}
}
//...
]
}
{
query:string,
corrected_query:string,
results:{
top_result:[Array], // Can be anything; video, playlist, artist etc..
songs:[
{
id:string,
title:string,
artist:string,
album:string,
duration:string,
thumbnails:[
Array
]
},
//...
],
videos:[
{
id:string,
title:string,
author:string,
views:string,
duration:string,
thumbnails:[Array]
},
//...
],
albums:[
{
id:string,
title:string,
author:string,
year:string,
thumbnails:[Array]
},
//...
],
featured_playlists:[
{
id:string,
title:string,
author:string,
channel_id:string,
total_items:number
},
//...
],
community_playlists:[
{
id:string,
title:string,
author:string,
channel_id:string,
total_items:number
},
//...
],
artists:[
{
id:string,
name:string,
subscribers:string,
thumbnails:[Array]
},
//...
]
}
}
const suggestions = await youtube.getSearchSuggestions('QUERY', { client: 'YOUTUBE' })
[
{
text: string, bold_text: string
},
//...
]
const video = await youtube.getDetails('VIDEO_ID');
{
title: string,
description: string,
thumbnail: {
url: string,
width: number,
height: number
},
metadata: {
embed: {
iframeUrl: string,
flashUrl: string,
width: number,
height: number,
flashSecureUrl: string
},
likes: number,
dislikes: number,
view_count: number,
average_rating: number,
length_seconds: number,
channel_id: string,
channel_url: string,
external_channel_id: string,
allow_ratings: boolean,
is_live_content: boolean,
is_family_safe: boolean,
is_unlisted: boolean,
is_private: boolean,
is_liked: boolean,
is_disliked: boolean,
is_subscribed: boolean,
subscriber_count: string,
current_notification_preference: string,
likes: { count: number, short_count_text: string },
publish_date_text: string,
has_ypc_metadata: boolean,
category: string,
channel_name: string,
publish_date: string,
upload_date: string,
keywords: [Array]
}
}
Sorting options: TOP_COMMENTS
| NEWEST_FIRST
const comments = await youtube.getComments('VIDEO_ID', 'TOP_COMMENTS');
Alternatively, you can use:
const video = await youtube.getDetails('VIDEO_ID');
const comments = await video.getComments();
{
page_count: number,
comment_count: number,
items: [
{
text: string,
author: {
name: string,
thumbnails: [
{
url: string,
width: number,
height: number
}
],
channel_id: string
},
metadata:{
published: string,
is_liked: boolean,
is_disliked: boolean,
is_pinned: boolean,
is_channel_owner: boolean,
is_reply: boolean,
like_count: number,
reply_count: number,
id: string
}
},
//...
]
}
Reply to, like/dislike, translate and report a comment:
const top_comment = comments.items[0];
await top_comment.like();
await top_comment.dislike();
await top_comment.report();
await top_comment.reply('Nice comment!');
// Note: only ISO language codes are accepted
await top_comment.translate('ru');
Comment replies:
const replies = await top_comment.getReplies();
Comments/replies continuation:
const continuation = await comments.getContinuation();
const replies_continuation = await replies.getContinuation();
const homefeed = await youtube.getHomeFeed();
{
videos: [
{
id: string,
title: string,
description: string,
channel: {
id: string,
name: string,
url: string
},
metadata: {
view_count: string,
short_view_count_text: { simple_text: string, accessibility_label: string },
thumbnail: {
url: string,
width: number,
height: number
},
moving_thumbnail: {
url: string,
width: number,
height: number
},
published: string,
duration: {
seconds: number,
simple_text: string,
accessibility_label: string
},
badges: string,
owner_badges: [Array]
}
},
// ...
]
}
Continuation:
const continuation = await homefeed.getContinuation();
const history = await youtube.getHistory();
{
items: [
{
date: string,
videos: [
{
id: string,
title: string,
channel: {
id: string,
name: string,
url: string
},
metadata: {
view_count: string,
short_view_count_text: {
simple_text: string,
accessibility_label: string
},
thumbnail: {
url: string,
width: number,
height: number
},
moving_thumbnail: {
url: string,
width: number,
height: number
},
published: string,
badges: [Array],
owner_badges: [Array]
}
},
//...
]
},
//...
]
}
Continuation:
const continuation = await history.getContinuation();
const mysubsfeed = await youtube.getSubscriptionsFeed();
{
items: [
{
date: string,
videos: [
{
id: string,
title: string,
description: string,
channel: {
id: string,
name: string,
url: string
},
metadata: {
view_count: string,
short_view_count_text: {
simple_text: string,
accessibility_label: string
},
thumbnail: {
url: string,
width: number,
height: number
},
moving_thumbnail: {
url: string,
width: number,
height: number
},
published: string,
badges: [Array],
owner_badges: [Array]
}
},
//...
]
},
//...
]
}
Continuation:
const continuation = await mysubsfeed.getContinuation();
const trending = await youtube.getTrending();
{
now: {
content: [
{
title: string,
videos: []
},
//...
]
},
// Other categories require an additional call to fetch videos
music: { getVideos: Promise.<Array> },
gaming: { getVideos: Promise.<Array> },
movies: { getVideos: Promise.<Array> }
}
const search = await youtube.search('Never give you up', { client: 'YTMUSIC' });
const lyrics = await youtube.getLyrics(search.results.songs[0].id);
const notifications = await youtube.getNotifications();
{
items: [
{
title: string,
sent_time: string,
channel_name: string,
channel_thumbnail: {
url: string,
width: number,
height: number
},
video_thumbnail: {
url: string,
width: number,
height: number
},
video_url: string,
read: boolean,
notification_id: string
},
//...
]
}
Continuation:
const continuation = await notifications.getContinuation();
Unseen notifications count:
const unread_notis_count = await youtube.getUnseenNotificationsCount();
Client: YOUTUBE
| YTMUSIC
const playlist = await youtube.getPlaylist('PLAYLIST_ID', { client: 'YOUTUBE' });
{
title: string,
description: string,
total_items: string,
last_updated: string,
views: string,
items: [
{
id: string,
title: string,
author: string,
duration: {
seconds: number,
simple_text: string,
accessibility_label: string
},
thumbnails: [Array]
},
//...
]
}
{
title: string,
description: string,
total_items: number,
duration: string,
year: string,
items: [
{
id: string,
title: string,
author: string,
duration: {
seconds: number,
simple_text: string
},
thumbnails: [Array]
},
//...
}
Don't forget that you must be signed in to use some of the following methods!
Subscribe/Unsubscribe:
await youtube.interact.subscribe('CHANNEL_ID');
await youtube.interact.unsubscribe('CHANNEL_ID');
Like/Dislike:
await youtube.interact.like('VIDEO_ID');
await youtube.interact.dislike('VIDEO_ID');
await youtube.interact.removeLike('VIDEO_ID');
Comment:
await youtube.interact.comment('VIDEO_ID', 'Haha, nice video!');
Playlists:
const videos = [
'VIDEO_ID1',
'VIDEO_ID2',
'VIDEO_ID3'
//...
];
// Create and delete a playlist:
await youtube.playlist.create('My Playlist', videos);
await youtube.playlist.delete('PLAYLIST_ID');
// Add and remove videos from a playlist:
await youtube.playlist.addVideos('PLAYLIST_ID', videos);
await youtube.playlist.removeVideos('PLAYLIST_ID', videos);
Translate (does not require sign in)
await youtube.interact.translate('Hi mom!', 'ru');
Change notification preferences:
Options: ALL
| NONE
| PERSONALIZED
await youtube.interact.setNotificationPreferences('CHANNEL_ID', 'ALL');
Response schema:
{
success: boolean,
status_code: number,
playlist_id?: string,
translated_content?: string,
data?: object
}
It is also possible to manage account settings:
Get account info:
await youtube.account.info();
{
name: string,
photo: [
{
url: string,
width: number,
height: number
}
],
country: string,
language: string;
}
Options: ON
| OFF
Subscription notifications:
await youtube.account.settings.notifications.setSubscriptions('ON');
Recommended content notifications:
await youtube.account.settings.notifications.setRecommendedVideos('ON');
Channel activity notifications:
await youtube.account.settings.notifications.setChannelActivity('ON');
Comment replies notifications:
await youtube.account.settings.notifications.setCommentReplies('ON');
Channel mention notifications:
await youtube.account.settings.notifications.setSharedContent('ON');
Options: ON
| OFF
Subscriptions privacy:
await youtube.account.settings.privacy.setSubscriptionsPrivate('ON');
Saved playlists privacy:
await youtube.account.settings.privacy.setSavedPlaylistsPrivate('ON');
Currently, the library can retrieve live chat messages, stats and also send messages.
const Innertube = require('youtubei.js');
async function start() {
const youtube = await new Innertube();
const search = await youtube.search('Lofi girl live');
const video = await youtube.getDetails(search.videos[0].id);
const livechat = video.getLivechat();
// Updated stats about the livestream
livechat.on('update-metadata', (data) => {
console.info('Info:', data);
});
// Fired whenever there is a new message or other chat events
livechat.on('chat-update', (message) => {
console.info(`- ${message.author.name}\n${message.text}\n\n`);
if(message.text == '!info') {
livechat.sendMessage('Hello! This message was sent from YouTube.js');
}
});
}
start();
Stop fetching the live chat:
livechat.stop();
Delete a message:
const msg = await livechat.sendMessage('Nice livestream!');
await msg.deleteMessage();
const options = {
format: string,
quality: string,
type: string,
range: { start: number, end: number }
};
const stream = youtube.download('VIDEO_ID', options);
Options:
format:
mp4
| webm
etc.. (note: only formats provided by YouTube are available)
quality:
144p
, 240p
, 360p
, 480p
, 720p
, 1080p
etc..
type:
video
| audio
| videoandaudio
range: indicates which bytes should be downloaded
Cancel a download:
stream.cancel();
Example:
const fs = require('fs');
const Innertube = require('youtubei.js');
async function start() {
const youtube = await new Innertube();
const search = await youtube.search('Looking for life on Mars - documentary');
const stream = youtube.download(search.videos[0].id, {
format: 'mp4', // Optional, defaults to mp4 and I recommend to leave it as it is unless you know what you're doing
quality: '360p', // if a video doesn't have a specific quality it'll fall back to 360p, also ignored when type is set to audio
type: 'videoandaudio' // can be “video”, “audio” and “videoandaudio”
});
stream.pipe(fs.createWriteStream(`./${search.videos[0].title}.mp4`));
stream.on('start', () => {
console.info('[DOWNLOADER]', 'Starting download now!');
});
stream.on('info', (info) => {
// { video_details: {..}, selected_format: {..}, formats: {..} }
console.info('[DOWNLOADER]', `Downloading ${info.video_details.title} by ${info.video_details.metadata.channel_name}`);
});
stream.on('progress', (info) => {
process.stdout.clearLine();
process.stdout.cursorTo(0);
process.stdout.write(`[DOWNLOADER] Downloaded ${info.percentage}% (${info.downloaded_size}MB) of ${info.size}MB`);
});
stream.on('end', () => {
process.stdout.clearLine();
process.stdout.cursorTo(0);
console.info('[DOWNLOADER]', 'Done!');
});
stream.on('error', (err) => console.error('[ERROR]', err));
}
start();
Alternatively, you can get the deciphered streaming data and handle the download yourself:
const streaming_data = await youtube.getStreamingData('VIDEO_ID', options);
{
selected_format: {
itag: number,
mimeType: string,
bitrate: number,
initRange: { start: string, end: string },
indexRange: { start: string, end: string },
lastModified: string,
contentLength: string,
quality: string,
projectionType: string,
averageBitrate: number,
highReplication: boolean,
audioQuality: string,
approxDurationMs: string,
audioSampleRate: string,
audioChannels: number,
loudnessDb: number,
url: string,
has_audio: boolean,
has_video: boolean
},
formats: [
{
itag: number,
mimeType: string,
bitrate: number,
initRange: { start: string, end: string },
indexRange: { start: string, end: string },
lastModified: string,
contentLength: string,
quality: string,
projectionType: string,
averageBitrate: number,
highReplication: boolean,
audioQuality: string,
approxDurationMs: string,
audioSampleRate: string,
audioChannels: number,
loudnessDb: number,
url: string,
has_audio: boolean,
has_video: boolean
}
//...
]
}
When signing in to your account, you have two options:
const fs = require('fs');
const Innertube = require('youtubei.js');
const creds_path = './yt_oauth_creds.json';
async function start() {
const creds = fs.existsSync(creds_path) && JSON.parse(fs.readFileSync(creds_path).toString()) || {};
const youtube = await new Innertube();
youtube.ev.on('auth', (data) => {
if (data.status === 'AUTHORIZATION_PENDING') {
console.info(`Hello!\nOn your phone or computer, go to ${data.verification_url} and enter the code ${data.code}`);
} else if (data.status === 'SUCCESS') {
fs.writeFileSync(creds_path, JSON.stringify(data.credentials));
console.info('Successfully signed-in, enjoy!');
}
});
youtube.ev.on('update-credentials', (data) => {
fs.writeFileSync(creds_path, JSON.stringify(data.credentials));
console.info('Credentials updated!', data);
});
await youtube.signIn(creds);
//...
}
start();
Sign out:
const response = await youtube.signOut();
if (response.success) {
console.log('You have successfully signed out');
}
const Innertube = require('youtubei.js');
async function start() {
const youtube = await new Innertube({ cookie: '...' });
//...
}
start();
Pull requests are welcome. For major changes, please open an issue first to discuss what you would like to change.
Please make sure to update tests as appropriate.
LuanRT - @lrt_nooneknows - luan.lrt4@gmail.com
Project Link: https://github.com/LuanRT/YouTube.js
This project is not affiliated with, endorsed, or sponsored by YouTube or any of their affiliates or subsidiaries. All trademarks, logos and brand names are the property of their respective owners.
Should you have any questions or concerns please contact me directly via email.
Distributed under the MIT License.
FAQs
A JavaScript client for YouTube's private API, known as InnerTube.
The npm package youtubei.js receives a total of 22,973 weekly downloads. As such, youtubei.js popularity was classified as popular.
We found that youtubei.js demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?
Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.
Security News
Research
A supply chain attack on Rspack's npm packages injected cryptomining malware, potentially impacting thousands of developers.
Research
Security News
Socket researchers discovered a malware campaign on npm delivering the Skuld infostealer via typosquatted packages, exposing sensitive data.
Security News
Sonar’s acquisition of Tidelift highlights a growing industry shift toward sustainable open source funding, addressing maintainer burnout and critical software dependencies.