Export your YouTube Watch Later list to CSV

Save every video in your Watch Later list (title, channel, duration and link) to a CSV or text file. Online playlist exporters can’t do this because the list is private.

export-playlist.js · 122 linespaste in the DevTools console on www.youtube.com
// Responsive Muse · YouTube Playlist Exporter · v1.1 · 2026-10-22
// Exports any playlist you can open (your own, Watch Later, Liked videos, public lists,
// or a channel's uploads) to CSV or TXT. Runs only on youtube.com/playlist.
// The script makes no network requests of its own: YouTube loads the rest of the list
// while the script scrolls. It never reads cookies, passwords or tokens.
(async () => {
  const TOOL = 'Export playlist';
  const FORMAT = 'csv';        // 'csv' (Excel, Google Sheets) or 'txt' (one URL per line)

  if (location.hostname !== 'www.youtube.com' || location.pathname !== '/playlist') {
    alert('Open a playlist first, for example:\nhttps://www.youtube.com/playlist?list=WL');
    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 = '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));
  // Playlist rows: new layout (yt-lockup-view-model) and old layout (ytd-playlist-video-renderer)
  const rows = () => [...document.querySelectorAll('yt-lockup-view-model, ytd-playlist-video-renderer')]
    .filter(el => el.querySelector('a[href*="/watch?v="][href*="index="]'));

  // 1. Scroll to the end until no new videos appear three times in a row
  let last = -1, idle = 0;
  while (idle < 3) {
    window.scrollTo(0, document.documentElement.scrollHeight);
    await sleep(1200);
    const n = rows().length;
    idle = n === last ? idle + 1 : 0;
    last = n;
    panel.text(`Loading the playlist… ${n} videos`);
  }

  // 2. Read every row
  const seen = new Set();
  const videos = [];
  for (const el of rows()) {
    const link = new URL(el.querySelector('a[href*="/watch?v="]').href);
    const id = link.searchParams.get('v');
    const index = link.searchParams.get('index') || '';
    if (seen.has(id + index)) continue;
    seen.add(id + index);
    const text = sel => (el.querySelector(sel)?.textContent || '').trim();
    videos.push({
      index,
      title: el.querySelector('h3')?.getAttribute('title') || text('#video-title'),
      channel: text('a[href^="/@"], a[href^="/channel/"], ytd-channel-name a'),
      duration: text('.ytBadgeShapeText, ytd-thumbnail-overlay-time-status-renderer #text'),
      url: 'https://www.youtube.com/watch?v=' + id,
    });
  }
  if (!videos.length) {
    panel.finish('No videos found. YouTube may have changed its layout: please report it on responsive-muse.com.');
    return 0;
  }

  // 3. Build the file and offer it
  const cell = v => '"' + String(v).replace(/"/g, '""') + '"';
  const body = FORMAT === 'txt'
    ? videos.map(v => v.url).join('\n')
    : ['Index,Title,Channel,Duration,URL', ...videos.map(v =>
        [v.index, v.title, v.channel, v.duration, v.url].map(cell).join(','))].join('\n');
  const name = (document.title.replace(/ - YouTube$/, '') || 'playlist').replace(/[\\/:*?"<>|]+/g, '_');
  deliver(`Found ${many(videos.length, 'video')}.`, body, `${name}.${FORMAT}`);
  return videos.length;
})();

Bookmarklet: drag the middle button to your bookmarks bar, open www.youtube.com and click the bookmark. It runs the same script with the options above.

Why it's safe to runAutomated check passed Oct 22, 2026 · tested on a real account Oct 22, 2026
  • Sends nothing anywhere. No fetch, XHR or external scripts. The only network activity is www.youtube.com itself loading your list.
  • Never touches your login. It doesn't read cookies, passwords, tokens or browser storage.
  • Only runs on www.youtube.com. Anywhere else it stops before doing anything.
  • Short and readable. 122 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: 7d5d08b0c3126c7b…
Verify it yourself in 30 seconds
  1. Read it: every line is above, with comments.
  2. Search the code for fetch, XMLHttpRequest, cookie, localStorage or eval. None of them appear.
  3. Open the Network tab before running it: you'll only see requests to www.youtube.com.
Did it work for you?

How to use it

  1. Open youtube.com/playlist?list=WL while signed in.
  2. Open the console: F12 or Ctrl+Shift+J (Cmd+Option+J on Mac). If Chrome asks, type allow pasting and press Enter.
  3. Copy the script, paste it in the console and press Enter. It scrolls to load the whole list, then shows a box: click Download (or Copy).

Why online tools can’t export Watch Later

Tools that ask for a playlist link read public playlists through YouTube’s API. Watch Later is private and only exists while you’re signed in, so the API returns nothing. This script runs inside your own tab and reads what’s already on your screen.

FAQ

Why can't online playlist exporters read Watch Later?

Watch Later is private and only visible while you're signed in. This script runs inside your own YouTube tab, so it sees what you see.

What columns does the CSV have?

Index, title, channel, duration and the video URL.

More YouTube tools