Save a Steam wishlist to a spreadsheet: rank, game, current price, original price, discount, the date each game was added and its store link. Works on your own wishlist and on any public one.
Works on your own wishlist (signed in) and on any public wishlist. Open it, then run the script.
// Responsive Muse · Steam Wishlist Exporter · v1.0 · 2026-10-22
// Saves a Steam wishlist (yours, or a friend's if it's public) to CSV or TXT: rank, game, price,
// discount, the date it was added and the store link. Handy to track sales in a spreadsheet or
// to share your list. Runs only on store.steampowered.com/wishlist. It reads the list the page
// already loaded and scrolls it to see every price. It makes no network requests of its own and
// never reads cookies, passwords or tokens.
(async () => {
const TOOL = 'Export Steam wishlist';
const FORMAT = 'csv'; // 'csv' (Excel, Sheets) or 'txt' ("Game - link", one per line)
if (location.hostname !== 'store.steampowered.com' || !location.pathname.startsWith('/wishlist')) {
alert('Open a wishlist first, for example your own:\nhttps://store.steampowered.com/wishlist/');
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));
// 1. The full list (order, priority, date added) comes with the page itself
let order = [];
const names = new Map();
try {
const cache = JSON.parse(window.SSR.renderContext.queryData).queries;
order = cache.find(q => q.queryKey[0] === 'WishlistSortedFiltered')?.state.data?.items || [];
for (const q of cache) {
const d = q.queryKey[0] === 'StoreItem' && q.state.data;
if (d?.appid && d.name) names.set(d.appid, d.name);
}
} catch (e) { /* older page: we'll rely on what is on screen */ }
// 2. Prices are only on screen: scroll the list and read each row as it appears
const seen = new Map();
const isPrice = t => t.length < 20 && /\d/.test(t) && /[€$£¥₹₩₽₺₴]|zł|kr|R\$|CHF|USD|ARS|CDN|Free|Gratis/i.test(t);
const collect = () => {
for (const row of document.querySelectorAll('[data-index]')) {
const a = [...row.querySelectorAll('a[href*="/app/"]')].find(x => x.textContent.trim());
const appid = Number((a?.getAttribute('href').match(/\/app\/(\d+)/) || [])[1]);
if (!appid || seen.has(appid)) continue;
const leaves = [...row.querySelectorAll('div, span')].filter(x => !x.childElementCount)
.map(x => x.textContent.trim()).filter(Boolean);
const discount = leaves.find(t => /^-\d+%$/.test(t)) || '';
const prices = leaves.filter(isPrice);
seen.set(appid, {
name: a.textContent.trim(),
price: prices[prices.length - 1] || '',
original: discount ? prices[0] || '' : '',
discount,
});
}
};
let scroller = document.querySelector('[data-index]');
while (scroller && !(scroller.scrollHeight > scroller.clientHeight + 50 && /auto|scroll/.test(getComputedStyle(scroller).overflowY))) {
scroller = scroller.parentElement;
}
scroller = scroller || document.scrollingElement;
let stop = false;
panel.stoppable(() => { stop = true; });
scroller.scrollTo(0, 0);
await sleep(800);
let idle = 0;
while (idle < 4 && !stop) {
const before = seen.size;
collect();
scroller.scrollBy(0, scroller.clientHeight * 0.8);
await sleep(900);
idle = seen.size === before ? idle + 1 : 0;
panel.text(`Reading the wishlist… ${seen.size}${order.length ? ' of ' + order.length : ''} games`);
}
// 3. Merge: every game of the list, with the price when we saw it
const ids = order.length ? order.map(o => o.appid) : [...seen.keys()];
const meta = new Map(order.map(o => [o.appid, o]));
const list = ids.map((id, i) => {
const s = seen.get(id) || {};
const added = meta.get(id)?.date_added;
return {
rank: i + 1,
name: s.name || names.get(id) || '',
price: s.price || '',
original: s.original || '',
discount: s.discount || '',
added: added ? new Date(added * 1000).toISOString().slice(0, 10) : '',
url: `https://store.steampowered.com/app/${id}/`,
};
});
if (!list.length) {
panel.finish('No games found. Is the wishlist empty or private? If not, Steam may have changed its layout: please report it on responsive-muse.com.');
return 0;
}
// 4. 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')
: ['Rank,Game,Price,Original price,Discount,Added,Store URL', ...list.map(g =>
[g.rank, g.name, g.price, g.original, g.discount, g.added, g.url].map(cell).join(','))].join('\n');
const owner = (document.title.match(/(?:of|de)\s+(.+)$/i) || [])[1] || 'steam';
deliver(`${stop ? 'Stopped early. ' : ''}Found ${many(list.length, 'game')}.`, body, `wishlist-${owner.replace(/[\\/:*?"<>|\s]+/g, '_')}.${FORMAT}`);
return list.length;
})();
Bookmarklet: drag the middle button to your bookmarks bar, open store.steampowered.com and click the bookmark. It runs the same script with the options above.
- Sends nothing anywhere. No fetch, XHR or external scripts. The only network activity is store.steampowered.com itself loading your list.
- Never touches your login. It doesn't read cookies, passwords, tokens or browser storage.
- Only runs on store.steampowered.com. Anywhere else it stops before doing anything.
- Short and readable. 158 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:
ea4e32e848c098b6…
Verify it yourself in 30 seconds
- Read it: every line is above, with comments.
- Search the code for
fetch,XMLHttpRequest,cookie,localStorageoreval. None of them appear. - Open the Network tab before running it: you'll only see requests to store.steampowered.com.
How to use it
- Open your wishlist (signed in) or a friend’s public wishlist.
- Open the console: F12 or Ctrl+Shift+J (Cmd+Option+J on Mac). If Chrome asks, type
allow pastingand press Enter. - Pick a format, copy the script, paste it in the console and press Enter. It scrolls the list to read every price, then shows a box: click Download (or Copy).
What you can do with it
- Track sales: export before and during a Steam sale and compare the Price and Discount columns.
- Budget: add up the Price column to see what your wishlist costs today.
- Share it: send the TXT version (“Game – link”) to friends before your birthday.
FAQ
Why is the price empty for some games?
Those games are no longer sold on Steam (delisted), so the store shows no price for them. They stay in your wishlist with their link.
Can I export a friend's wishlist?
Yes, if their profile and wishlist are public, or visible to you as a friend. Open their wishlist and run the script.
Can I track sales with it?
Export it now and again later: the Price, Original price and Discount columns show what changed. Prices are in the currency of your Steam store.
More free tools
YouTube
8 tools- Clear Watch Later
- Unsubscribe from all channels
- Unlike all videos
- Export Watch Later
- Export saved posts
- Delete all comments
TikTok
1 tools- Export profile videos
Amazon
2 tools- Export order history
- Export wishlist
X / Twitter
3 tools- Export bookmarks
- Unlike all posts
- Remove all bookmarks
AI chats
4 tools- Export ChatGPT chat
- Export Claude chat
- Export Gemini chat
- Export Perplexity thread
Google Maps
1 tools- Export Google Maps reviews
Steam
1 tools- Export Steam wishlist
Apple Music
1 tools- Export Apple Music playlist
