Unofficial YouTube Music API client for TypeScript/JavaScript. A complete TypeScript port of the Python ytmusicapi library.
- Full TypeScript support with comprehensive type definitions
- Works in Node.js 18+ (uses native
fetch) - Supports both ESM and CommonJS
- Cookie-based authentication for accessing personal library
- All major YouTube Music features: search, browse, playlists, library, uploads, podcasts
npm install ytmusicapiimport { YTMusic } from 'ytmusicapi';
// Create an unauthenticated client
const ytm = new YTMusic();
// Search for songs
const results = await ytm.search('Daft Punk', { filter: 'songs' });
console.log(results[0].title, results[0].artists);
// Get artist details
const artist = await ytm.getArtist('UC_kRDKYrUlrbtrSiyu5Tflg');
console.log(artist.name, artist.subscribers);
// Get album details
const album = await ytm.getAlbum('MPREb_K8qWMWVqXGi');
console.log(album.title, album.tracks.length);Some features require authentication (library access, playlist management, etc.). Authentication uses browser cookies.
- Open YouTube Music in your browser
- Open Developer Tools (F12)
- Go to Network tab
- Play a song or navigate
- Find a request to
music.youtube.com - Copy these headers:
Cookie,X-Goog-AuthUser(if present)
// Option 1: JSON file with headers
const ytm = new YTMusic({ auth: './headers.json' });
// Option 2: JSON string
const ytm = new YTMusic({
auth: JSON.stringify({ cookie: 'your_cookie_string_here' })
});
// Option 3: Headers object
const ytm = new YTMusic({
auth: { cookie: 'SAPISID=xxx; __Secure-3PAPISID=xxx; ...' }
});
// Check if authenticated
console.log(ytm.isAuthenticated); // true{
"cookie": "__Secure-3PAPISID=xxx; __Secure-3PSID=xxx; SAPISID=xxx; ...",
"x-goog-authuser": "0"
}Required cookies: __Secure-3PAPISID (or SAPISID), __Secure-3PSID, LOGIN_INFO
interface YTMusicOptions {
auth?: string | BrowserAuthHeaders; // Authentication
user?: string; // Brand account user ID
language?: string; // Language code (default: 'en')
location?: string; // Country code (default: 'US')
}
const ytm = new YTMusic({
language: 'es',
location: 'ES'
});Search YouTube Music for songs, albums, artists, playlists, or videos.
const results = await ytm.search('never gonna give you up');
// With filter
const songs = await ytm.search('daft punk', {
filter: 'songs',
limit: 20
});
// With scope (library only - requires auth)
const librarySongs = await ytm.search('favorite', {
filter: 'songs',
scope: 'library'
});Parameters:
| Name | Type | Description |
|---|---|---|
query |
string |
Search query |
options.filter |
'songs' | 'albums' | 'artists' | 'playlists' | 'videos' | 'community_playlists' | 'featured_playlists' |
Filter results by type |
options.scope |
'library' | 'uploads' |
Search scope (requires auth) |
options.limit |
number |
Maximum results (default: 20) |
options.ignoreSpelling |
boolean |
Ignore spelling corrections |
Returns: Promise<SearchResult[]>
Get search suggestions/autocomplete.
const suggestions = await ytm.getSearchSuggestions('never gonna');
// ['never gonna give you up', 'never gonna let you down', ...]Returns: Promise<SearchSuggestion[]>
Get personalized home page content.
const sections = await ytm.getHome(5);
for (const section of sections) {
console.log(section.title);
console.log(section.contents);
}Returns: Promise<HomeSection[]>
Get artist information and discography.
const artist = await ytm.getArtist('UC_kRDKYrUlrbtrSiyu5Tflg');
console.log(artist.name); // 'Daft Punk'
console.log(artist.subscribers); // '8.42M subscribers'
console.log(artist.songs?.results); // Top songs
console.log(artist.albums?.results); // Albums
console.log(artist.singles?.results); // SinglesParameters:
| Name | Type | Description |
|---|---|---|
channelId |
string |
Artist channel ID (starts with UC) |
Returns: Promise<ArtistDetail>
interface ArtistDetail {
name: string;
channelId: string;
description?: string;
subscribers?: string;
views?: string;
thumbnails: Thumbnail[];
songs?: { browseId?: string; results: PlaylistItem[] };
albums?: { browseId?: string; params?: string; results: AlbumSummary[] };
singles?: { browseId?: string; params?: string; results: AlbumSummary[] };
videos?: { browseId?: string; results: VideoItem[] };
related?: { browseId?: string; results: RelatedArtist[] };
shuffleId?: string;
radioId?: string;
}Get full list of artist's albums (when artist page shows "Show all").
const artist = await ytm.getArtist('UC_kRDKYrUlrbtrSiyu5Tflg');
if (artist.albums?.params) {
const allAlbums = await ytm.getArtistAlbums(
artist.channelId,
artist.albums.params
);
}Returns: Promise<AlbumSummary[]>
Get album details including tracks.
const album = await ytm.getAlbum('MPREb_K8qWMWVqXGi');
console.log(album.title); // 'Random Access Memories'
console.log(album.artists); // [{ name: 'Daft Punk', id: '...' }]
console.log(album.year); // '2013'
console.log(album.trackCount); // 13
console.log(album.duration); // '1 hour, 14 minutes'
console.log(album.tracks); // Array of tracksParameters:
| Name | Type | Description |
|---|---|---|
browseId |
string |
Album browse ID (starts with MPREb_) |
Returns: Promise<AlbumDetail>
interface AlbumDetail {
title: string;
type: string; // 'Album', 'Single', 'EP'
thumbnails: Thumbnail[];
description?: string;
artists: ArtistRef[];
year?: string;
trackCount?: number;
duration?: string;
audioPlaylistId?: string;
tracks: PlaylistItem[];
}Convert an audio playlist ID (OLAK...) to a browse ID (MPREb_...).
const browseId = await ytm.getAlbumBrowseId('OLAK5uy_l...');
const album = await ytm.getAlbum(browseId);Returns: Promise<string | null>
Get song metadata and streaming information.
const song = await ytm.getSong('dQw4w9WgXcQ');
console.log(song.title);
console.log(song.author);
console.log(song.lengthSeconds);
console.log(song.viewCount);
console.log(song.streamingData?.adaptiveFormats); // Stream URLsReturns: Promise<SongDetail>
Get lyrics for a song. The browseId comes from getWatchPlaylist().
const watchPlaylist = await ytm.getWatchPlaylist({ videoId: 'dQw4w9WgXcQ' });
if (watchPlaylist.lyrics) {
const lyrics = await ytm.getLyrics(watchPlaylist.lyrics);
console.log(lyrics?.lyrics);
console.log(lyrics?.source);
}Returns: Promise<Lyrics | null>
Get related content for a song. The browseId comes from getWatchPlaylist().
const watchPlaylist = await ytm.getWatchPlaylist({ videoId: 'dQw4w9WgXcQ' });
if (watchPlaylist.related) {
const related = await ytm.getSongRelated(watchPlaylist.related);
}Returns: Promise<HomeSection[]>
Get a user's public profile.
const user = await ytm.getUser('UCxxxxxxxx');
console.log(user.name);
console.log(user.thumbnails);Returns: Promise<UserProfile>
Get a user's public playlists.
const playlists = await ytm.getUserPlaylists('UCxxxxxxxx');Returns: Promise<PlaylistItem[]>
Get the "Up Next" queue/watch playlist for a video or playlist.
// From a video
const watchPlaylist = await ytm.getWatchPlaylist({
videoId: 'dQw4w9WgXcQ'
});
// From a playlist
const watchPlaylist = await ytm.getWatchPlaylist({
playlistId: 'PLxxxxxxxx'
});
console.log(watchPlaylist.tracks); // Queue tracks
console.log(watchPlaylist.lyrics); // Lyrics browse ID
console.log(watchPlaylist.related); // Related content browse ID
console.log(watchPlaylist.playlistId); // Current playlist IDParameters:
| Name | Type | Description |
|---|---|---|
options.videoId |
string |
Video ID to start from |
options.playlistId |
string |
Playlist ID |
options.limit |
number |
Maximum tracks (default: 25) |
options.radio |
boolean |
Get radio/mix playlist |
options.shuffle |
boolean |
Shuffle the playlist |
Returns: Promise<WatchPlaylist>
Get a radio/mix playlist based on a song.
const radio = await ytm.getRadioPlaylist('dQw4w9WgXcQ');
console.log(radio.tracks); // Similar songsReturns: Promise<WatchPlaylist>
Get mood and genre categories for browsing.
const moods = await ytm.getMoodCategories();
// { 'Mood': [{ title: 'Chill', params: '...' }, ...], ... }
for (const [category, items] of Object.entries(moods)) {
console.log(category);
for (const mood of items) {
console.log(` ${mood.title}`);
}
}Returns: Promise<MoodCategories>
Get playlists for a specific mood/genre.
const moods = await ytm.getMoodCategories();
const chillParams = moods['Mood'].find(m => m.title === 'Chill')?.params;
if (chillParams) {
const playlists = await ytm.getMoodPlaylists(chillParams);
}Returns: Promise<MoodPlaylists>
Get music charts for a country.
const charts = await ytm.getCharts('US');
console.log(charts.songs); // Top songs
console.log(charts.videos); // Top videos
console.log(charts.artists); // Top artists
console.log(charts.trending); // TrendingReturns: Promise<ChartsResult>
Get new album releases.
const releases = await ytm.getNewReleases();
console.log(releases.title);
console.log(releases.albums);Returns: Promise<{ title: string; albums: unknown[] }>
Get user's library playlists.
const playlists = await ytm.getLibraryPlaylists({ limit: 50 });
for (const playlist of playlists) {
console.log(playlist.title, playlist.count);
}Options:
| Name | Type | Description |
|---|---|---|
limit |
number |
Maximum results |
order |
'a_to_z' | 'z_to_a' | 'recently_added' |
Sort order |
Returns: Promise<LibraryPlaylist[]>
Get user's liked/added songs.
const songs = await ytm.getLibrarySongs({ limit: 100 });Returns: Promise<LibrarySong[]>
Get user's saved albums.
const albums = await ytm.getLibraryAlbums();Returns: Promise<LibraryAlbum[]>
Get artists from user's library.
const artists = await ytm.getLibraryArtists();Returns: Promise<LibraryArtist[]>
Get user's subscribed artists.
const subscriptions = await ytm.getLibrarySubscriptions();Returns: Promise<LibrarySubscription[]>
Get user's "Liked Music" playlist.
const liked = await ytm.getLikedSongs(100);Returns: Promise<PlaylistItem[]>
Get user's playback history.
const history = await ytm.getHistory();
for (const item of history) {
console.log(item.title, item.played);
}Returns: Promise<HistoryItem[]>
Remove an item from playback history.
const history = await ytm.getHistory();
if (history[0].feedbackToken) {
await ytm.removeHistoryItem(history[0].feedbackToken);
}Returns: Promise<boolean>
Rate a song (like/dislike/remove rating).
await ytm.rateSong('dQw4w9WgXcQ', 'LIKE');
await ytm.rateSong('dQw4w9WgXcQ', 'DISLIKE');
await ytm.rateSong('dQw4w9WgXcQ', 'INDIFFERENT'); // Remove ratingParameters:
| Name | Type | Description |
|---|---|---|
videoId |
string |
Video ID |
rating |
'LIKE' | 'DISLIKE' | 'INDIFFERENT' |
Rating |
Returns: Promise<boolean>
Subscribe to artists.
await ytm.subscribeArtists(['UC_kRDKYrUlrbtrSiyu5Tflg']);Returns: Promise<boolean>
Unsubscribe from artists.
await ytm.unsubscribeArtists(['UC_kRDKYrUlrbtrSiyu5Tflg']);Returns: Promise<boolean>
Get account information.
const account = await ytm.getAccountInfo();
console.log(account.accountName);
console.log(account.channelHandle);Returns: Promise<AccountInfo>
Get playlist contents.
const playlist = await ytm.getPlaylist('PLxxxxxxxx', 100);
console.log(playlist.title);
console.log(playlist.author);
console.log(playlist.trackCount);
console.log(playlist.duration);
console.log(playlist.tracks);Parameters:
| Name | Type | Description |
|---|---|---|
playlistId |
string |
Playlist ID (with or without VL prefix) |
limit |
number | null |
Maximum tracks (null for all) |
suggestions |
number |
Number of suggestions to include |
Returns: Promise<PlaylistDetail>
Create a new playlist.
const playlistId = await ytm.createPlaylist({
title: 'My Playlist',
description: 'A great playlist',
privacyStatus: 'PRIVATE',
videoIds: ['dQw4w9WgXcQ', 'abc123']
});Options:
| Name | Type | Description |
|---|---|---|
title |
string |
Playlist title (required) |
description |
string |
Playlist description |
privacyStatus |
'PUBLIC' | 'PRIVATE' | 'UNLISTED' |
Privacy (default: PRIVATE) |
videoIds |
string[] |
Initial video IDs |
sourcePlaylist |
string |
Clone from existing playlist |
Returns: Promise<string> (new playlist ID)
Edit playlist metadata.
await ytm.editPlaylist({
playlistId: 'PLxxxxxxxx',
title: 'New Title',
description: 'New description',
privacyStatus: 'PUBLIC'
});Returns: Promise<boolean>
Delete a playlist.
await ytm.deletePlaylist('PLxxxxxxxx');Returns: Promise<boolean>
Add songs to a playlist.
const result = await ytm.addPlaylistItems({
playlistId: 'PLxxxxxxxx',
videoIds: ['dQw4w9WgXcQ', 'abc123']
});
// result.playlistEditResults contains setVideoIds for removalOptions:
| Name | Type | Description |
|---|---|---|
playlistId |
string |
Target playlist ID |
videoIds |
string[] |
Video IDs to add |
sourcePlaylist |
string |
Add all from another playlist |
duplicates |
boolean |
Allow duplicates (default: false) |
Returns: Promise<{ playlistEditResults: Array<{ videoId: string; setVideoId?: string }> }>
Remove songs from a playlist.
await ytm.removePlaylistItems({
playlistId: 'PLxxxxxxxx',
videos: [
{ videoId: 'dQw4w9WgXcQ', setVideoId: 'setVideoIdFromAddResult' }
]
});Returns: Promise<boolean>
Get uploaded songs.
const uploads = await ytm.getLibraryUploadSongs();Returns: Promise<UploadedSong[]>
Get uploaded albums.
const albums = await ytm.getLibraryUploadAlbums();Returns: Promise<UploadedAlbum[]>
Get uploaded artists.
const artists = await ytm.getLibraryUploadArtists();Returns: Promise<UploadedArtist[]>
Get songs by an uploaded artist.
const songs = await ytm.getLibraryUploadArtist('FEmusic_library_privately_owned_artist_...');Returns: Promise<UploadedSong[]>
Get tracks from an uploaded album.
const tracks = await ytm.getLibraryUploadAlbum('FEmusic_library_privately_owned_release_...');Returns: Promise<UploadedSong[]>
Delete an uploaded song/album.
await ytm.deleteUploadEntity('entityIdFromUploadedSong');Returns: Promise<boolean>
Get podcast channel information.
const channel = await ytm.getChannel('UCxxxxxxxx');
console.log(channel.title);
console.log(channel.episodes);Returns: Promise<PodcastChannel>
Get all episodes from a podcast channel.
const episodes = await ytm.getChannelEpisodes('UCxxxxxxxx');Returns: Promise<PodcastEpisode[]>
Get podcast show details.
const podcast = await ytm.getPodcast('PLxxxxxxxx');
console.log(podcast.title);
console.log(podcast.author);
console.log(podcast.episodes);Returns: Promise<PodcastDetail>
Get episode details.
const episode = await ytm.getEpisode('dQw4w9WgXcQ');
console.log(episode.title);
console.log(episode.description);
console.log(episode.duration);Returns: Promise<EpisodeDetail>
Get user's saved podcast episodes.
const saved = await ytm.getSavedEpisodes();Returns: Promise<PodcastEpisode[]>
Get user's subscribed podcasts.
const podcasts = await ytm.getLibraryPodcasts();Returns: Promise<Array<{ title: string; browseId: string; thumbnails: unknown[] }>>
Change the language for API requests.
ytm.setLanguage('es'); // Spanish
ytm.setLanguage('fr'); // French
ytm.setLanguage('de'); // GermanChange the country/region for API requests.
ytm.setLocation('ES'); // Spain
ytm.setLocation('JP'); // Japan
ytm.setLocation('BR'); // BrazilFetch and store a visitor ID (improves API reliability).
await ytm.fetchVisitorId();Returns: Promise<string | undefined>
interface Thumbnail {
url: string;
width: number;
height: number;
}interface ArtistRef {
name: string;
id: string | null;
}interface PlaylistItem {
videoId: string;
title: string;
artists: ArtistRef[];
album?: { name: string; id?: string };
duration?: string;
duration_seconds?: number;
thumbnails: Thumbnail[];
isExplicit: boolean;
isAvailable: boolean;
likeStatus?: 'LIKE' | 'DISLIKE' | 'INDIFFERENT';
feedbackTokens?: { add?: string; remove?: string };
setVideoId?: string;
trackNumber?: number;
}interface SearchResult {
resultType: 'song' | 'video' | 'album' | 'artist' | 'playlist';
category?: string;
// Type-specific fields...
}The library throws typed errors for different scenarios:
import {
YTMusicError, // Base error class
YTMusicAuthError, // Authentication required/failed
YTMusicUserError, // Invalid input
YTMusicServerError, // API errors
YTMusicNavigationError // Response parsing errors
} from 'ytmusicapi';
try {
const playlists = await ytm.getLibraryPlaylists();
} catch (error) {
if (error instanceof YTMusicAuthError) {
console.log('Need to authenticate first');
} else if (error instanceof YTMusicServerError) {
console.log('API error:', error.statusCode, error.message);
}
}For custom implementations:
import { YTMusicBase, ENDPOINTS } from 'ytmusicapi';
class CustomYTMusic extends YTMusicBase {
async customMethod() {
return this.sendRequest(ENDPOINTS.BROWSE, {
browseId: 'FEmusic_home'
});
}
}For parsing raw API responses:
import { nav, findObjectByKey } from 'ytmusicapi';
const title = nav(response, ['header', 'title', 'runs', 0, 'text'], true);
const shelf = findObjectByKey(contents, 'musicShelfRenderer');MIT
This is a TypeScript port of ytmusicapi by sigma67.