Export a Deezer playlist to CSV or text, free (no sign-in to other sites)

Download any Deezer playlist, album or your Favourite tracks as a spreadsheet or a plain list: title, artist, album, duration and album link. Free, in your own browser, without installing anything or giving a transfer site access to your account.

Works on Deezer for the web (deezer.com) on Windows, Mac, Linux and Chromebook. Open the playlist, then run the script.

export-deezer-playlist.js · 137 linespaste in the DevTools console on www.deezer.com
// Responsive Muse · Deezer Playlist Exporter · v1.0 · 2026-11-12
// Saves any Deezer playlist, album or Favourite tracks list that you can open on deezer.com
// to CSV or TXT: title, artists, album, duration and album link. Useful to back up a playlist
// or move it to Spotify, Apple Music, YouTube 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 Deezer playlist';
  const FORMAT = 'csv';        // 'csv' (Excel, Sheets, playlist converters) or 'txt' ("Artist - Title", one per line)

  if (!/(^|\.)deezer\.com$/.test(location.hostname) || !/\/(playlist|album)\/\d+|\/profile\/\d+\/loved/.test(location.pathname)) {
    alert('Open a playlist, an album or your Favourite tracks on deezer.com first, for example:\nhttps://www.deezer.com/en/playlist/3155776842');
    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 clean = s => (s || '').replace(/\s+/g, ' ').trim();
  const grid = document.querySelector('[aria-rowcount]');
  const expected = grid ? Number(grid.getAttribute('aria-rowcount')) : 0;
  const heading = clean(document.querySelector('[data-testid="masthead"] h1, [data-testid="masthead"] h2')?.textContent);
  // album pages only name the album's artist once, at the top, and don't link the album in each row
  const onAlbum = /\/album\//.test(location.pathname);
  const pageAlbum = onAlbum ? heading : '';
  const pageArtist = onAlbum ? clean(document.querySelector('[data-testid="masthead-subtitle"]')?.textContent) : '';
  const pageUrl = onAlbum ? 'https://www.deezer.com/album/' + location.pathname.match(/\/album\/(\d+)/)[1] : '';

  // Read the songs currently on screen (rows are recycled while scrolling: key = row number)
  const collect = () => {
    for (const row of (grid || document).querySelectorAll('[role="row"][aria-rowindex]')) {
      const n = Number(row.getAttribute('aria-rowindex'));
      const title = row.querySelector('[data-testid="title"]');
      if (!title || !(n > 0) || tracks.has(n)) continue;
      const album = row.querySelector('a[data-testid="album"]');
      tracks.set(n, {
        n,
        // the title carries the row number for screen readers ("12. Song"): drop it
        title: clean(title.textContent).replace(new RegExp('^' + n + '\\.\\s*'), ''),
        artist: [...new Set([pageArtist, ...[...row.querySelectorAll('[data-testid="artist"]')].map(x => clean(x.textContent))])].filter(Boolean).join(', '),
        album: clean(album?.textContent) || pageAlbum,
        duration: clean(row.querySelector('[data-testid="duration"]')?.textContent).replace(/^0(\d:)/, '$1'),
        url: album ? 'https://www.deezer.com/' + album.getAttribute('href').replace(/^\/([a-z]{2}(-[a-z]{2})?\/)?/i, '') : pageUrl,
      });
    }
  };

  // 1. Scroll to the end, collecting as we go, until nothing new appears three times in a row
  let stop = false;
  panel.stoppable(() => { stop = true; });
  window.scrollTo(0, 0);
  await sleep(800);
  let idle = 0;
  while (idle < 3 && !stop && !(expected && tracks.size >= expected)) {
    const before = tracks.size;
    collect();
    [...document.querySelectorAll('[role="row"][aria-rowindex]')].pop()?.scrollIntoView({ block: 'start' });
    await sleep(700);
    collect();
    idle = tracks.size === before ? idle + 1 : 0;
    panel.text(`Collecting songs… ${tracks.size}${expected ? ' of ' + expected : ''}`);
  }
  const list = [...tracks.values()].sort((x, y) => x.n - y.n);
  if (!list.length) {
    panel.finish('No songs found. Is the playlist empty or private? If not, Deezer 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,Album URL', ...list.map(t =>
        [t.n, t.title, t.artist, t.album, t.duration, t.url].map(cell).join(','))].join('\n');
  const missing = expected && list.length < expected ? ` (the playlist says ${expected}; scroll down and run it again if some are missing)` : '';
  const name = (heading || 'deezer-playlist').replace(/[\\/:*?"<>|]+/g, '_');
  deliver(`${stop ? 'Stopped early. ' : ''}Found ${many(list.length, 'song')}${missing}.`, 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: 137 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 Deezer playlist button to your bookmarks bar and click it on www.deezer.com. No warning that way.

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

Why it's safe to runAutomated check passed Nov 9, 2026 · tested on a real account Nov 12, 2026
  • Sends nothing anywhere. No fetch, XHR or external scripts. The only network activity is www.deezer.com itself loading your list.
  • Never touches your login. It doesn't read cookies, passwords, tokens or browser storage.
  • Only runs on www.deezer.com. Anywhere else it stops before doing anything.
  • Short and readable. 137 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: 907f18aa44ef6f61…
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.deezer.com.
Did it work for you?

How to use it

  1. Open the playlist on deezer.com in a computer browser. Public playlists and albums work without signing in; for your own playlists or Favourite tracks, sign in first.
  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 through the list and then offers the file.

What you get

One row per song with its position, title, artists, album, duration and album link. Choose TXT for a plain “Artist – Title” list that playlist converters and search boxes understand.

Why not a transfer site?

Transfer sites ask you to sign in to Deezer and to the other service, and their free plans limit how many songs you can move. Deezer’s own data request takes days. This script reads the list you already have on screen and sends nothing anywhere.

FAQ

Doesn't Deezer have an export button?

No. You can request your personal data from Deezer, but it arrives days later and not as a playlist file. This script saves the playlist you have open in a few seconds.

Do I need to log in or connect my account?

Not for public playlists and albums: they open without signing in. Your own playlists and Favourite tracks need you to be signed in. You never authorize any app.

Can I move the playlist to Spotify, Apple Music or TIDAL?

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.

Does it work with long playlists?

Yes. Deezer only keeps a few rows on screen, so the script scrolls and collects them all.

More music tools

Export & back up