Skip to content

Instantly share code, notes, and snippets.

@abraxas86
Last active March 9, 2026 23:21
Show Gist options
  • Select an option

  • Save abraxas86/ad72ba46b6cdd86dc63058bba0c629c2 to your computer and use it in GitHub Desktop.

Select an option

Save abraxas86/ad72ba46b6cdd86dc63058bba0c629c2 to your computer and use it in GitHub Desktop.
Export collecitons on itch.io as csv files
// ==UserScript==
// @name Itch Collection CSV Exporter
// @namespace https://github.com/abraxas86/tampermonkey-scripts/blob/main/itch.io/
// @version 4.7
// @description Scroll down to the bottom of your collection, click the button, get CSV of your collection!
// @author Abraxas86
// @match https://itch.io/c/*
// @match https://itch.io/my-purchases
// @match https://itch.io/b/*
// @match https://itch.io/bundle/*
// @match https://itch.io/s/*
// @require https://gist.github.com/raw/2625891/waitForKeyElements.js
// @require https://raw.githubusercontent.com/eligrey/FileSaver.js/master/src/FileSaver.js
// @grant none
// @icon https://itch.io//static/images/itchio-square-144.png
// ==/UserScript==
/* globals jQuery, $, waitForKeyElements, saveAs */
// Thank you to LeeThompson for the duplicate title fix, and KindaArtsy for the category scrape!
(function() {
'use strict';
const games = [];
let mode = "null";
let gameID = "";
let imagePath = "";
let Title = "";
let Synopsis = "";
let Author = "";
let Genre = "";
let Category = "";
let URL = "";
let Blurb = "";
let Price = ""; //price_value, only available in grid view
let output = "gameid,category,title,author,synopsis,imagepath,url,blurb,price\n";
let filename = $('.grid_header > h2:nth-child(1)').text();
if(!filename) {
filename = $('.stat_header_widget .text_container h2 .object_title').text();
}
waitForKeyElements(".game_link", makeRed);
if (document.querySelector('[class*=bundle_download_page]')) {
mode = "bundle";
} else if (document.querySelector('[class*=game_list]')) {
mode = "list";
} else if (document.querySelector('[id^=game_grid]') || document.querySelector('[class*=bundle]')) {
mode = "grid";
} else {
mode = "Error";
}
//alert(mode);
$('.footer').prepend('<span class="csvButton">Export to CSV</span>&nbsp;&nbsp;&nbsp;<input type="text" id="fileName" class="csvText" value=""> <span class="extension">.csv</span><p></p>');
$('#fileName').attr("value", filename);
$('.csvButton').css({'color':'white','background-color':'grey','border-radius':'10px','padding':'15px','cursor':'pointer'});
$('.extension').css({'font-size':'14pt'});
$('.csvText').css({'padding':'5px','border':'none','border-radius':'10px','font-size':'13pt','background-color':'#555555','color':'#BCBCBC','text-align':'right'});
function makeRed() {
$('.game_link').css("color", "red");
}
$('.csvButton').click(function() {
// These elements will mess up our data for the CSV.
$('.gif_label').remove();
console.log("======= Game Package Data =======");
// GRID MODE
if (mode == 'grid') {
$('.game_cell').each(function() {
const cell = $(this);
// Itch Game ID
gameID = cell.attr('data-game_id');
console.log("gameID: " + gameID);
// Path to thumbnail
imagePath = cell.find('.lazy_loaded').attr('src');
console.log("imagePath: " + imagePath);
// Game Title Note: .title is for bundle compatibility
Title = cell.find('.game_link, .title').text().replace(/"/g, '""');
console.log("Title: " + Title);
// Game URL Note: .title is for bundle compatibility
URL = cell.find('.game_title a, .title').attr('href');
console.log("URL: " + URL);
// Game Synopsis Note: .short_text is for bundle compatibility
Synopsis = cell.find('.game_text, .short_text').text().replace(/"/g, '""');
console.log("Synopsis: " + Synopsis);
// Game Author
Author = cell.find('.game_author, .user_link').text().replace(/"/g, '""');
console.log("Author: " + Author);
// Price
let onSale = cell.find('a[title^="Pay"]');
if (onSale.length) {
Price = onSale.attr('title').replace(/Pay | or more for this game/g, '').trim();
} else {
Price = cell.find('.price_value').text().trim();
}
console.log("Price: " + Price);
// Game Category (from tag link)
Category = cell.find('.cell_tags a').attr('href');
if(Category == undefined){
Category = "games";
} else {
Category = Category.replace(/\//g, "");
}
console.log("Category: " + Category);
// Game Blurb (user-created comment about library item)
Blurb = cell.find('.blurb_drop').text().trim().replace(/"/g, '""');
console.log("Blurb: " + Blurb);
// Build Array to push to CSV File, sanitizing data to prevent commas in scraped data from screwing things up
games.push(`"${gameID}","${Category}","${Title}","${Author}","${Synopsis}","${imagePath}","${URL}","${Blurb}","${Price}"`);
});
}
// LIST MODE
else if (mode == 'list') {
$('.game_row').each(function() {
const row = $(this);
// Game Title
Title = row.find('.game_link, .title').text().replace(/"/g, '""');
console.log("Title: " + Title);
// Itch Game ID
gameID = row.find('.game_cell').attr('data-game_id');
console.log("gameID: " + gameID);
// Path to thumbnail
imagePath = row.find('.game_thumb').attr('src');
console.log("imagePath: " + imagePath);
// Game URL
URL = row.find('.game_title').attr('href');
console.log("URL: " + URL);
// Game Synopsis (not avaiable in list mode)
Synopsis = "";
console.log("Synopsis: " + Synopsis);
// Game Author
Author = row.find('.author_link').text().replace(/"/g, '""');
console.log("Author: " + Author);
// Game Category (from tag link)
Category = row.find('.cell_tags a').attr('href');
if(Category === undefined){
Category = "games";
} else {
Category = Category.replace(/\//g, "");
}
console.log("Category: " + Category);
// Game Blurb
Blurb = row.find('.blurb_drop').text().trim().replace(/"/g, '""');
console.log("Blurb: " + Blurb);
// Price (not available in list mode)
Price = "";
console.log("Price: " + Price);
// Build Array to push to CSV File, sanitizing data to prevent commas in scraped data from screwing things up
games.push(`"${gameID}","${Category}","${Title}","${Author}","${Synopsis}","${imagePath}","${URL}","${Blurb}","${Price}"`);
});
}
// BUNDLE MODE
else if (mode == 'bundle') {
$('.game_row').each(function() {
const row = $(this);
// Path to thumbnail
imagePath = row.find('.game_thumb').attr('data-background_image');
console.log("imagePath: " + imagePath);
// Game URL
URL = row.find('.game_title a').attr('href');
console.log("URL: " + URL);
// Game Title
Title = row.find('.game_title a').text().replace(/"/g, '""');
console.log("Title: " + Title);
// Game Synopsis
Synopsis = row.find('.game_short_text').text().trim().replace(/"/g, '""');
console.log("Synopsis: " + Synopsis);
// Game Author
Author = row.find('.game_author a').text().replace(/"/g, '""');
console.log("Author: " + Author);
// GameID (not available in bundle lists)
gameID = "";
console.log("gameID: " + gameID);
// Game Category (from tag link)
Category = row.find('.cell_tags a').attr('href');
if(Category === undefined){
Category = "games";
} else {
Category = Category.replace(/\//g, "");
}
console.log("Category: " + Category);
// Game Blurb (not available in bundle lists)
Blurb = "";
console.log("Blurb: " + Blurb);
// Price (not available in bundle lists)
Price = "";
console.log("Price: " + Price);
// Build Array to push to CSV File
games.push(`"${gameID}","${Category}","${Title}","${Author}","${Synopsis}","${imagePath}","${URL}","${Blurb}","${Price}"`);
});
}
else {
alert("Error: Unable to correctly identify code.");
}
// Format array for CSV output, sanitizing for titles with commas,
// and adding a newline at the end of each title
for (let i = 0; i < games.length; i++) {
output += games[i] + "\n";
}
filename = document.getElementById("fileName").value;
if (filename === "") { $('.gif_label').remove();
filename = "collection";
}
filename = filename + ".csv";
const blob = new Blob([output], {
type: "text/plain;charset=utf-8"
});
saveAs(blob, filename);
});
})();
@abraxas86
Copy link
Author

4.5

Forgot to remove a line of debug code. The functionality it self was also broken and I didn't catch it. It's good now I hope. It passed the tests I threw at it, at least.

@LeeThompson
Copy link

LeeThompson commented Aug 6, 2025

4.5

Getting duplicate titles with grid view.

e.g.
Brütal LegendBrütal Legend

Changing to the following seemed to fix it:

                // Game Title Note  Note: .title is for bundle compatibility
                Title = cell.find('.game_link, .title').text().replace(/"/g, '""');
                console.log("Title: " + Title);

@KindaArtsy
Copy link

4.5

IntuitiveThinker on Itch had asked if there was a way to differentiate different types of content (games, books, physical games, soundtracks, etc). I couldn't find any way to do this, so I was thinking that adding it to the Blurb is probably the best way to manage it. If Itch ever expands to allow this type of info to be added, I will update the script to reflect the change.

hey i figured out a way to do this, at least in grid mode while on a bundle page (https://itch.io/b/*)

Category = cell.find('.cell_tags a').attr('href');
  if(Category == undefined){
    Category = "games";
  } else {
    Category = Category.replace(/\//g, "");
  }

a bit ugly but it works

@LeeThompson
Copy link

4.5

IntuitiveThinker on Itch had asked if there was a way to differentiate different types of content (games, books, physical games, soundtracks, etc). I couldn't find any way to do this, so I was thinking that adding it to the Blurb is probably the best way to manage it. If Itch ever expands to allow this type of info to be added, I will update the script to reflect the change.

hey i figured out a way to do this, at least in grid mode while on a bundle page (https://itch.io/b/*)

Category = cell.find('.cell_tags a').attr('href');
  if(Category == undefined){
    Category = "games";
  } else {
    Category = Category.replace(/\//g, "");
  }

a bit ugly but it works

if(Category === undefined){ may be safer but I'm not a JS expert.

@abraxas86
Copy link
Author

abraxas86 commented Feb 18, 2026

Ahh, sorry for ghosting here... life got lifey. I just updated the script to 4.6 - one day I'll learn how to do proper versioning haha

4.6 adds:

  • Bugfix, title scrape hopefully works better now (thanks to @LeeThompson)
  • Category scrape - will give you an idea if it's a game, asset, physical game, ect. (thanks @KindaArtsy)

@abraxas86
Copy link
Author

Babe wake up, 4.7 just dropped!

The older versions of the script were stripping out the price labels on games, which in honesty was kinda bad form, since it made it difficult to see how much things were at a glance.

This new version keeps them in place AND ads the price value to the CSV.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment