Export your Reddit saved posts to CSV or Markdown

Back up everything you saved on Reddit, posts and comments, to a spreadsheet, a Markdown note for Notion or Obsidian, or a plain list of links. Free, in your browser, no app to authorize.

Runs on old.reddit.com (same account, simpler pages that don't change). Sign in, open your saved list, then run the script.

export-saved.js · 148 linespaste in the DevTools console on old.reddit.com
// Responsive Muse · Reddit Saved Posts Exporter · v1.0 · 2026-10-22
// Saves everything you saved on Reddit (posts and comments) to CSV, Markdown or TXT: subreddit,
// title, author, date, link and the text of saved comments. Reddit's own list stops at about
// 1,000 items, so export it before it fills up. Runs only on old.reddit.com, whose pages stay
// the same for years: it opens your saved list page by page in a hidden frame of the same site,
// like clicking "next" yourself. It makes no network requests of its own and never reads
// cookies, passwords or tokens.
(async () => {
  const TOOL = 'Export Reddit saved posts';
  const FORMAT = 'csv';        // 'csv' (Excel, Sheets), 'md' (Notion, Obsidian) or 'txt' (links only)

  if (location.hostname !== 'old.reddit.com' || !/^\/user\/[^/]+\/saved/.test(location.pathname)) {
    alert('Open your saved list on old Reddit first (signed in):\nhttps://old.reddit.com/user/me/saved/');
    return;
  }
  // Must be your own list: old Reddit shows the signed-in user in the header
  const me = (document.querySelector('#header .user a[href*="/user/"]')?.textContent || '').trim();
  const owner = (location.pathname.split('/')[2] || '').toLowerCase();
  if (owner !== me.toLowerCase()) {
    alert(me
      ? `This isn't your own list. Open:\nhttps://old.reddit.com/user/${me}/saved/`
      : 'Sign in on old.reddit.com first, then open your own saved list.');
    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));

  // Load one page of the list in a hidden frame of the same site 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 saved items of one page
  const items = new Map();
  const read = doc => {
    for (const t of doc.querySelectorAll('#siteTable > .thing[data-fullname]')) {
      const id = t.dataset.fullname;
      if (items.has(id)) continue;
      const comment = id.startsWith('t1_');
      const ts = Number(t.dataset.timestamp);
      items.set(id, {
        type: comment ? 'comment' : 'post',
        subreddit: t.dataset.subredditPrefixed || (t.dataset.subreddit ? 'r/' + t.dataset.subreddit : ''),
        title: (t.querySelector('a.title')?.textContent || '').trim(),
        author: t.dataset.author || '',
        date: ts ? new Date(ts).toISOString().slice(0, 10) : '',
        score: t.dataset.score || '',
        text: comment ? (t.querySelector('.usertext-body .md')?.innerText || '').trim() : '',
        link: t.dataset.url && !t.dataset.url.startsWith('/r/') ? t.dataset.url : '',
        permalink: 'https://www.reddit.com' + (t.dataset.permalink || ''),
      });
    }
    return doc.querySelector('.next-button a')?.getAttribute('href') || '';
  };

  // 1. Page through the whole list
  let stop = false;
  panel.stoppable(() => { stop = true; });
  let next = read(document);
  let page = 1;
  while (next && !stop) {
    panel.text(`Reading page ${++page}… ${many(items.size, 'item')} so far`);
    const doc = await loadPage(next);
    if (!doc) break;
    next = read(doc);
    await sleep(1000);   // a human pace between pages
  }
  const list = [...items.values()];
  if (!list.length) {
    panel.finish('Nothing saved yet, or Reddit changed its layout. If you have saved items, 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(i => i.permalink).join('\n')
    : FORMAT === 'md' ? list.map(i => `- **${i.subreddit}** · ${i.date} · [${(i.title || 'comment').replace(/[[\]]/g, '')}](${i.permalink})` +
        (i.link ? ` · [link](${i.link})` : '') + (i.text ? `\n  > ${i.text.replace(/\n/g, '\n  > ')}` : '')).join('\n')
    : ['Type,Subreddit,Title,Author,Date,Score,Comment text,Link,Reddit URL',
       ...list.map(i => [i.type, i.subreddit, i.title, i.author, i.date, i.score, i.text, i.link, i.permalink].map(cell).join(','))].join('\n');
  deliver(`${stop ? 'Stopped early. ' : ''}Found ${many(list.length, 'saved item')}.`, body, `reddit-saved.${FORMAT}`);
  return list.length;
})();

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

How to use it

  1. Open old.reddit.com/user/me/saved 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. Pick a format, copy the script, paste it in the console and press Enter. It reads every page of your saved list, then shows a box: click Download (or Copy).

Why export your saved posts?

Reddit only keeps your latest 1,000 saved items and has no search for them. Once the list is full, the oldest ones silently drop off. A CSV gives you a searchable copy you can sort by subreddit or date, and the Markdown format turns it into a reading list.

FAQ

Why old.reddit.com?

It is the same Reddit with the same account and the same saved list, but its pages have kept the same structure for years, so the script keeps working when the new design changes.

Is there a limit?

Reddit only keeps your latest 1,000 saved items. Export them before the list fills up: older ones drop off and can't be recovered.

Does it include saved comments?

Yes. Saved comments come with their text, subreddit, date and a link to the thread.

More free tools

Clean up