Save any YouTube Music playlist, album or your Liked Music to a spreadsheet: title, artist, album, duration and link. Free, in your browser, without connecting your Google account to any converter.
Works on YouTube Music for the web (music.youtube.com) on any computer. Open the playlist, then run the script.
// Responsive Muse · YouTube Music Playlist Exporter · v1.0 · 2026-10-31
// Saves any YouTube Music playlist or album you can open on music.youtube.com (your own playlists,
// your Liked Music or public ones) to CSV or TXT: title, artist, album, duration and link. Useful
// to back up a playlist or move it to Spotify, Apple Music or Tidal. Long playlists load while
// the script scrolls. It makes no network requests of its own and never reads cookies,
// passwords or tokens.
(async () => {
const TOOL = 'Export YouTube Music playlist';
const FORMAT = 'csv'; // 'csv' (Excel, Sheets, playlist converters) or 'txt' ("Artist - Title", one per line)
if (location.hostname !== 'music.youtube.com' || !/^\/(playlist|browse)/.test(location.pathname)) {
alert('Open a playlist on YouTube Music first, for example your Liked Music:\nhttps://music.youtube.com/playlist?list=LM');
return;
}
// A small box on the page shows progress and asks before changing anything,
// so you never need to read or edit the console.
const many = (n, word) => `${n} ${word}${n === 1 ? '' : 's'}`; // 1 video, 2 videos
const panel = (() => {
const box = document.createElement('div');
box.style.cssText = 'display:block!important;position:fixed;right:16px;bottom:16px;z-index:2147483647;width:340px;max-width:calc(100vw - 32px);' +
'background:#111318;color:#e6e8ee;font:14px/1.5 system-ui,sans-serif;border-radius:12px;padding:16px;' +
'box-shadow:0 8px 30px rgba(0,0,0,.4);text-align:left';
const head = document.createElement('strong');
head.textContent = TOOL;
const msg = document.createElement('p');
msg.style.cssText = 'margin:8px 0 12px;white-space:pre-line';
const row = document.createElement('div');
row.style.cssText = 'display:flex;flex-wrap:wrap;gap:8px;justify-content:flex-end';
box.append(head, msg, row);
document.body.append(box);
const button = (label, primary, onClick) => {
const b = document.createElement('button');
b.textContent = label;
b.style.cssText = 'font:600 14px system-ui,sans-serif;padding:8px 14px;border-radius:8px;cursor:pointer;' +
(primary ? 'background:#0f766e;color:#fff;border:1px solid #0f766e' : 'background:none;color:#e6e8ee;border:1px solid #3a3f4b');
b.onclick = onClick;
row.append(b);
};
return {
text(t) { msg.textContent = t; },
ask(t, yes) {
msg.textContent = t;
row.replaceChildren();
return new Promise(r => {
button('Cancel', false, () => { row.replaceChildren(); r(false); });
button(yes, true, () => { row.replaceChildren(); r(true); });
});
},
stoppable(onStop) { row.replaceChildren(); button('Stop', false, onStop); },
actions(t, list) {
msg.textContent = t;
row.replaceChildren();
list.forEach(([label, primary, run]) => button(label, primary, run));
},
finish(t) { this.actions(t, [['Close', true, () => box.remove()]]); },
close() { box.remove(); },
};
})();
// Offer the result as a downloaded file or as clipboard text (a real click, so it always works)
const deliver = (summary, body, file) => {
const save = () => {
const a = document.createElement('a');
a.href = URL.createObjectURL(new Blob([/\.csv$/.test(file) ? '\ufeff' + body : body], { type: 'text/plain;charset=utf-8' }));
a.download = file;
a.click();
setTimeout(() => URL.revokeObjectURL(a.href), 5000);
panel.text(`${summary}\nSaved as ${file} (check your Downloads).`);
};
const copy = () => navigator.clipboard.writeText(body)
.then(() => panel.text(`${summary}\nCopied. Paste it into a spreadsheet or a note.`))
.catch(() => panel.text(`${summary}\nThe browser blocked the clipboard: use Download instead.`));
panel.actions(summary, [['Close', false, () => panel.close()], ['Copy', false, copy], ['Download', true, save]]);
};
const sleep = ms => new Promise(r => setTimeout(r, ms));
const tracks = new Map();
const text = el => (el?.textContent || '').replace(/\s+/g, ' ').trim();
// Read the songs currently on the page (YouTube Music keeps every loaded row in the page)
const collect = () => {
for (const row of document.querySelectorAll('ytmusic-responsive-list-item-renderer')) {
const title = text(row.querySelector('.title-column .title') || row.querySelector('.title'));
if (!title) continue;
// Secondary columns: artist(s), then the album (only when it links to an album page;
// on music videos and episodes that column holds the upload date instead)
const cols = [...row.querySelectorAll('.secondary-flex-columns yt-formatted-string')].map(text);
const hasAlbum = !!row.querySelector('.secondary-flex-columns a[href^="browse/"]');
const watch = row.querySelector('a[href*="watch?v="]');
const id = watch ? new URL(watch.href, location.origin).searchParams.get('v') : '';
const link = id ? 'https://music.youtube.com/watch?v=' + id : (row.querySelector('.title a')?.href || '');
const key = link || title + '|' + cols[0];
if (tracks.has(key)) continue;
tracks.set(key, {
n: tracks.size + 1,
title,
artist: cols[0] || '',
album: hasAlbum ? cols[1] || '' : '',
duration: text(row.querySelector('.fixed-column yt-formatted-string, .fixed-columns yt-formatted-string')),
url: link,
});
}
};
// YouTube Music scrolls an inner panel (div#content), not the window: find it from the first row
const scroller = (() => {
for (let e = document.querySelector('ytmusic-responsive-list-item-renderer'); e && e !== document.body; e = e.parentElement) {
if (e.scrollHeight > e.clientHeight + 50 && /auto|scroll/.test(getComputedStyle(e).overflowY)) return e;
}
return document.scrollingElement;
})();
// 1. Scroll to the bottom again and again: YouTube Music loads the next 100 songs each time
let stop = false;
panel.stoppable(() => { stop = true; });
let idle = 0;
while (idle < 4 && !stop) {
const before = tracks.size;
collect();
scroller.scrollTop = scroller.scrollHeight;
document.querySelector('ytmusic-continuation-item-renderer')?.scrollIntoView({ block: 'end' });
await sleep(1500);
idle = tracks.size === before ? idle + 1 : 0;
panel.text(`Collecting songs… ${tracks.size}`);
}
const list = [...tracks.values()];
if (!list.length) {
panel.finish('No songs found. YouTube Music may have changed its layout: please report it on responsive-muse.com.');
return 0;
}
// 2. Build the file and offer it
const cell = v => '"' + String(v).replace(/"/g, '""') + '"';
const body = FORMAT === 'txt'
? list.map(t => `${t.artist} - ${t.title}`).join('\n')
: ['#,Title,Artist,Album,Duration,URL', ...list.map(t =>
[t.n, t.title, t.artist, t.album, t.duration, t.url].map(cell).join(','))].join('\n');
const name = (document.title.replace(/ - YouTube Music$/, '') || 'playlist').replace(/[\\/:*?"<>|]+/g, '_');
deliver(`${stop ? 'Stopped early. ' : ''}Found ${many(list.length, 'song')}.`, body, `${name}.${FORMAT}`);
return list.length;
})();
🛡️ Chrome says “Don't paste code…”? That's normal. Type allow pasting in the console, press Enter and paste again. You only need to do it once.
Why does Chrome warn me?
Chrome shows this the first time anyone pastes into the console, to protect people from scams that ask them to paste unknown code. It's right to warn you, and that's why this script is fully visible above: 140 commented lines that send nothing anywhere and never touch your passwords or cookies. Why it's safe.
Rather not use the console? Drag the Export YouTube Music playlist button to your bookmarks bar and click it on music.youtube.com. No warning that way.
Bookmarklet: drag the middle button to your bookmarks bar, open music.youtube.com and click the bookmark. It runs the same script with the options above.
- Sends nothing anywhere. No fetch, XHR or external scripts. The only network activity is music.youtube.com itself loading your list.
- Never touches your login. It doesn't read cookies, passwords, tokens or browser storage.
- Only runs on music.youtube.com. Anywhere else it stops before doing anything.
- Short and readable. 140 commented lines, not minified or obfuscated.
- Shows its progress on the page. A small box tells you what it's doing and offers Download or Copy at the end.
- Same code for everyone. SHA-256 of the default version:
08d5442e2f3900b8…
Verify it yourself in 30 seconds
- Read it: every line is above, with comments.
- Search the code for
fetch,XMLHttpRequest,cookie,localStorageoreval. None of them appear. - Open the Network tab before running it: you'll only see requests to music.youtube.com.
How to use it
- Open the playlist on music.youtube.com. Public playlists work without signing in; for your Liked Music open
music.youtube.com/playlist?list=LM. - Open the console: F12 or Ctrl+Shift+J (Cmd+Option+J on Mac). If Chrome asks, type
allow pastingand press Enter. - Copy the script, paste it in the console and press Enter. It scrolls through the list (100 songs at a time) and then offers the file.
What you get
One row per song with its position, title, artist, album (when YouTube Music shows one), duration and link. Choose TXT to get a plain “Artist – Title” list that playlist converters and search boxes understand.
Why not an online converter?
Transfer sites ask you to sign in with Google and give them access to your YouTube account, and the free plans limit how many songs you can move. This script needs no permission at all: it reads the playlist already on your screen.
FAQ
Doesn't YouTube Music have an export button?
No. Google Takeout can export your playlists, but you wait for an email and get one file per playlist with video IDs only. This script gives you titles, artists and albums in a minute.
Can I move the playlist to Spotify or Apple Music?
Yes. Export it as CSV and import the file with a playlist converter, or use the TXT format ("Artist - Title" per line) and paste it into one.
Why are there fewer songs than the playlist says?
YouTube Music hides songs that are no longer available (deleted or blocked in your country), so they can't be exported. We tested a 496-track playlist: 475 were visible and all 475 were exported.
Do I need to sign in?
Not for public playlists and albums. Your own playlists and Liked Music need you to be signed in on music.youtube.com.
