Export your Steam library to CSV or Excel, free (hours played included)

Download the list of every game you own on Steam as a spreadsheet: hours played, last played date, achievements and store link. Free, in your own browser, with no Steam API key and without connecting your account to any site.

Sign in on steamcommunity.com and open your Games page (Profile > Games > All games), then run the script. Works on a friend's library too if their game details are public.

export-library.js · 126 linespaste in the DevTools console on steamcommunity.com
// Responsive Muse · Steam Library Exporter · v1.0 · 2026-10-31
// Saves a Steam game library (yours, or a friend's if their game details are public) to CSV or TXT:
// game, hours played, last played, achievements and store link. Handy to back up your list, see
// what you never played or share it. Runs only on a steamcommunity.com profile's Games page. It
// reads the list the page already loaded: it makes no network requests of its own and never reads
// cookies, passwords or tokens.
(async () => {
  const TOOL = 'Export Steam library';
  const FORMAT = 'csv';        // 'csv' (Excel, Sheets) or 'txt' ("Game - link", one per line)
  const SORT = 'hours';        // 'hours' (most played first), 'name' (A to Z) or 'recent' (last played first)

  if (location.hostname !== 'steamcommunity.com' || !/^\/(id|profiles)\/[^/]+\/games/.test(location.pathname)) {
    alert('Open a Games page first, for example your own:\nhttps://steamcommunity.com/my/games/?tab=all');
    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]]);
  };

  // 1. The whole library (and achievement progress) comes with the page itself
  let games = [];
  const ach = new Map();
  try {
    const cache = JSON.parse(window.SSR.renderContext.queryData).queries;
    games = cache.find(q => q.queryKey[0] === 'OwnedGames')?.state.data || [];
    for (const q of cache) {
      const d = q.queryKey[0] === 'AchievementProgress' && q.state.data;
      if (d?.appid) ach.set(d.appid, d);
    }
  } catch (e) { /* not loaded yet, handled below */ }
  if (!Array.isArray(games) || !games.length) {
    panel.finish('No games found. Reload this page (F5) and run the script again. If the list is still empty, the profile\'s game details are private or it has no games.');
    return 0;
  }

  // 2. One row per game
  const day = t => t ? new Date(t * 1000).toISOString().slice(0, 10) : '';
  const hours = m => m ? (m / 60).toFixed(1) : '0';
  const list = games.map(g => {
    const a = ach.get(g.appid);
    return {
      name: g.name || `App ${g.appid}`,
      minutes: g.playtime_forever || 0,
      recent: g.playtime_2weeks || 0,
      last: g.rtime_last_played || 0,
      achievements: a?.total ? `${a.unlocked}/${a.total}` : '',
      percent: a?.total ? Math.round(a.unlocked * 100 / a.total) + '%' : '',
      appid: g.appid,
      url: `https://store.steampowered.com/app/${g.appid}/`,
    };
  });
  const byName = (x, y) => x.name.localeCompare(y.name, undefined, { sensitivity: 'base' });
  list.sort(SORT === 'name' ? byName
    : SORT === 'recent' ? (x, y) => y.last - x.last || byName(x, y)
    : (x, y) => y.minutes - x.minutes || byName(x, y));

  // 3. Build the file and offer it
  const cell = v => '"' + String(v).replace(/"/g, '""') + '"';
  const body = FORMAT === 'txt'
    ? list.map(g => `${g.name} - ${g.url}`).join('\n')
    : ['Game,Hours played,Hours last 2 weeks,Last played,Achievements,Achievements %,App ID,Store URL', ...list.map(g =>
        [g.name, hours(g.minutes), hours(g.recent), day(g.last), g.achievements, g.percent, g.appid, g.url].map(cell).join(','))].join('\n');
  const total = Math.round(list.reduce((s, g) => s + g.minutes, 0) / 60);
  const never = list.filter(g => !g.minutes).length;
  const owner = (document.title.split('::')[1] || 'steam').trim();
  deliver(`Found ${many(list.length, 'game')}: ${total} hours played, ${never} never played.`, body,
    `steam-library-${owner.replace(/[\\/:*?"<>|\s]+/g, '_')}.${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: 126 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 Steam library button to your bookmarks bar and click it on steamcommunity.com. No warning that way.

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

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

How to use it

  1. Sign in on steamcommunity.com and open your Games page (Profile > Games > All games).
  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 the list the page already loaded and offers the file straight away.

What you get

One row per game: name, hours played, hours in the last 2 weeks, last played date, achievements unlocked (for example 40/50) and percentage, App ID and store link. The box also tells you your total hours and how many games you never played. Choose TXT for a plain “Game – link” list.

Why not a website or an API key?

Most Steam library exporters ask for a Steam Web API key, need your profile to be public, or are scripts you have to install. This one is short, readable and runs only on steamcommunity.com, in your own tab, with the session you already have.

FAQ

Does Steam have an export button for my library?

No. Steam only lets you browse the list. You can request your account data from Steam Support, but that takes a while and comes as a web page, not a spreadsheet. This script gives you the CSV in a second.

Do I need an API key or to give my password?

No. Unlike exporters that ask for a Steam Web API key or your profile to be public, the script reads the Games page you already have open, signed in as you. It sends nothing anywhere.

Why are some games missing?

Free games you never launched, removed betas and Family Sharing games from other accounts may not appear on your Games page, so they can't be exported. Everything the page lists is exported.

Why is the achievements column empty for some games?

Those games have no Steam achievements, or Steam doesn't report them on the Games page.

Can I export a friend's library?

Yes, if their profile's game details are public (or visible to friends and you are one). Open their Games page and run the script. If it says 0 games, their game details are private.

More Steam tools

Export & back up