Export your Audible library to CSV or Excel, free (Goodreads-ready)

Download every audiobook in your Audible library as a spreadsheet: title, author, narrator, series, whether you finished it, progress and link. Free, in your own browser, from any Audible store, without installing an app or giving anyone your password.

Sign in on your Audible store (audible.com, .co.uk, .de, .es, .fr…) in a computer browser and open Library, then run the script. It reads every page of your library by itself.

export-library.js · 145 linespaste in the DevTools console on www.audible.com
// Responsive Muse · Audible Library Exporter · v1.0 · 2026-11-12
// Saves your whole Audible library to CSV or TXT: title, authors, narrators, series, status
// (finished, in progress, not started), progress, length or time left, ASIN and link. Audible has
// no export button. Runs only on your Library page of any Audible store (audible.com, .co.uk,
// .de, .es…). It reads your library page by page in a hidden frame of the same Audible site, like
// clicking "Next" yourself. It never sends anything anywhere and never reads cookies, passwords
// or tokens.
(async () => {
  const TOOL = 'Export Audible library';
  const FORMAT = 'csv';        // 'csv' (Excel, Sheets, Goodreads, StoryGraph) or 'txt' ("Title - Author", one per line)

  if (!/(^|\.)audible\.[a-z.]+$/.test(location.hostname) || !/^\/library\/titles/.test(location.pathname)) {
    alert('Open your Audible Library first (signed in), for example:\nhttps://www.audible.com/library/titles');
    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 clean = s => (s || '').replace(/\s+/g, ' ').trim();
  const afterLabel = li => clean(li?.innerText || li?.textContent).replace(/^[^:]{1,30}:\s*/, '');   // "By: Name" → "Name"

  // Load one page of your own library in a hidden frame and wait for it
  const loadPage = path => new Promise(resolve => {
    const frame = document.createElement('iframe');
    frame.style.cssText = 'position:absolute;left:-9999px;width:1000px;height:800px';
    const done = doc => { resolve(doc); setTimeout(() => frame.remove(), 0); };
    frame.onload = () => done(frame.contentDocument);
    setTimeout(() => done(null), 30000);
    frame.src = new URL(path, location.origin).href;
    document.body.appendChild(frame);
  });

  // Read the titles of one page
  const readPage = doc => [...doc.querySelectorAll('[id^="adbl-library-content-row-"]')].map(row => {
    const asin = row.id.split('-').pop();
    const lis = [...row.querySelectorAll('li')];
    const by = lis.find(li => li.querySelector('a[href*="/author/"], a[href*="searchAuthor"]'));
    const narr = lis.find(li => li.querySelector('a[href*="searchNarrator"]'));
    const finished = doc.getElementById('time-remaining-finished-' + asin);
    const time = doc.getElementById('time-remaining-display-' + asin);
    const meter = row.querySelector('[aria-valuenow]');
    const timeText = time && !time.classList.contains('bc-pub-hidden') ? clean(time.textContent) : '';
    const isFinished = finished && !finished.classList.contains('bc-pub-hidden');
    const status = isFinished ? 'Finished' : meter ? 'In progress' : timeText ? 'Not started' : '';
    return {
      asin,
      title: clean(row.querySelector('.bc-size-headline3')?.textContent),
      author: by ? afterLabel(by) : '',
      narrator: narr ? afterLabel(narr) : '',
      series: afterLabel(row.querySelector('li.seriesLabel')).replace(/\s+,/g, ','),
      status,
      progress: isFinished ? '100%' : meter ? meter.getAttribute('aria-valuenow') + '%' : status ? '0%' : '',
      length: status === 'Not started' ? timeText : '',
      left: status === 'In progress' ? timeText : '',
      url: `${location.origin}/pd/${asin}`,
    };
  });

  // 1. Every page of the library, 50 titles at a time
  let stop = false;
  panel.stoppable(() => { stop = true; });
  const books = new Map();
  for (let page = 1; page <= 200 && !stop; page++) {
    panel.text(`Reading page ${page}… ${many(books.size, 'title')} so far`);
    const doc = await loadPage(`/library/titles?pageSize=50&page=${page}`);
    if (!doc) break;
    const before = books.size;
    for (const b of readPage(doc)) if (!books.has(b.asin)) books.set(b.asin, b);
    if (books.size === before) break;   // past the last page Audible repeats it or shows none
  }
  const list = [...books.values()];
  if (!list.length) {
    panel.finish('No titles found. Are you signed in and is this your Library? If so, Audible 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(b => b.author ? `${b.title} - ${b.author}` : b.title).join('\n')
    : ['Title,Author,Narrator,Series,Status,Progress,Length,Time left,ASIN,URL', ...list.map(b =>
        [b.title, b.author, b.narrator, b.series, b.status, b.progress, b.length, b.left, b.asin, b.url].map(cell).join(','))].join('\n');
  const done = list.filter(b => b.status === 'Finished').length;
  const reading = list.filter(b => b.status === 'In progress').length;
  deliver(`${stop ? 'Stopped early. ' : ''}Found ${many(list.length, 'title')}: ${done} finished, ${reading} in progress.`, body,
    `audible-library.${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: 145 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 Audible library button to your bookmarks bar and click it on www.audible.com. No warning that way.

Bookmarklet: drag the middle button to your bookmarks bar, open www.audible.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.audible.com itself loading your list.
  • Never touches your login. It doesn't read cookies, passwords, tokens or browser storage.
  • Only runs on www.audible.com. Anywhere else it stops before doing anything.
  • Short and readable. 145 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: 65bc88cac86c6880…
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.audible.com.
Did it work for you?

How to use it

  1. Sign in on your Audible store in a computer browser and open Library (for example audible.com/library/titles, audible.co.uk/library/titles).
  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 reads every page of your library, 50 titles at a time, and then offers the file.

What you get

One row per title: title, authors, narrators, series and book number, status (finished, in progress or not started), progress percentage, length (or time left for books you are listening to), ASIN and link. The box also tells you how many books you have finished. Choose TXT for a plain “Title – Author” list.

Why export your Audible library?

Audible has no export, so a list of your audiobooks is the only way to import them into Goodreads or StoryGraph, keep a backup if you cancel your membership, or sort your library by what you haven’t started yet. Apps that do it ask you to sign in to Amazon through them; this script reads the page you already have open and sends nothing anywhere.

FAQ

Doesn't Audible have an export button?

No. The website and the app only let you browse your library; there is no way to download it as a list. This script saves it as a spreadsheet in a few seconds.

What does it export?

Title, authors, narrators, series and book number, status (finished, in progress or not started), progress percentage, length or time left, ASIN and a link to each book. Podcasts are listed with their title and link.

Can I import it into Goodreads or StoryGraph?

Yes. Both accept a CSV with title and author: export as CSV and use their import page, or use the TXT format ("Title - Author") to search for the books.

Does it work with big libraries?

Yes. It reads your library 50 titles at a time, in a hidden frame of the same Audible site, until the last page.

Is it safe?

The script runs in your own Audible tab, reads only your Library pages and sends nothing anywhere. It never touches your password and doesn't change anything in your account.