Skip to content

Instantly share code, notes, and snippets.

@milichev
Created November 27, 2025 19:57
Show Gist options
  • Select an option

  • Save milichev/bca2f30d5339c39ea898b1f8f87e6612 to your computer and use it in GitHub Desktop.

Select an option

Save milichev/bca2f30d5339c39ea898b1f8f87e6612 to your computer and use it in GitHub Desktop.
Delete all unsaved creations in Google Photos
/**
* @fileoverview Google Photos Unsolicited Creations Bulk Deleter.
* * This script automates the deletion of all auto-generated, unsaved creations
* (Collages, Animations, Stylized Photos, etc.) found on the Google Photos
* unsaved creations page (https://photos.google.com/unsaved).
* * It handles:
* 1. Sequential deletion to avoid race conditions.
* 2. Virtual scrolling to load all items progressively.
* 3. Robust retry logic to confirm the visual removal of each item.
* * -----------------------------------------------------------------------------
* * # How to Use
* * 1. **Navigate:** Open the Google Photos "Creations" page:
* `https://photos.google.com/unsaved`
* 2. **Open Console:** Press F12 (or Cmd+Option+J on Mac) to open the browser
* DevTools console.
* 3. **Paste & Run:** Copy the entire contents of this script and paste it into
* the console. Press Enter.
* 4. **Monitor:** The script will start logging its progress, showing how many
* creations it finds and deletes in each pass. Do not interact with the page
* while the script is running.
* 5. **Finish:** The script will log "Deletion complete. Total items deleted: X."
* when it reaches the end of the page and no more items can be loaded.
*/
const sleep = (ms) => new Promise(resolve => setTimeout(resolve, ms));
const DELETE_ITEM_SELECTOR = 'li[aria-label="Delete permanently"]';
const CARD_SELECTOR = 'div[jscontroller][jsmodel][jsdata*=";itm:"]';
function findDeleteItems() {
return document.querySelectorAll(DELETE_ITEM_SELECTOR);
}
function findCardContainer(deleteItem) {
let current = deleteItem;
while (current && current !== document.body) {
if (current.matches(CARD_SELECTOR)) {
return current;
}
current = current.parentElement;
}
return null;
}
function isCardDeleted(card) {
return (card.offsetWidth === 0 && card.offsetHeight === 0) ||
card.querySelector(':scope > div > div')?.textContent === 'Card deleted';
}
function filterVisible(deleteItems) {
const visibleCreations = [];
for (const deleteItem of deleteItems) {
const card = findCardContainer(deleteItem);
if (card && !isCardDeleted(card)) {
visibleCreations.push({
card,
deleteItem
});
}
}
return visibleCreations;
}
async function deleteCreative(card, deleteItem) {
const postClickWaitTimeMs = 200;
const maxAttemptCount = 20;
let attemptCount = 0;
deleteItem.click();
while (attemptCount < maxAttemptCount) {
await sleep(postClickWaitTimeMs);
if (isCardDeleted(card)) {
return; // Success, card is gone
}
attemptCount++;
}
// If the loop finishes without the card disappearing, throw an error
throw new Error(`Deletion failed after ${postClickWaitTimeMs * maxAttemptCount} ms: Card remains visible.`);
}
async function bulkDeleteUnsolicitedCreations() {
let deletedCount = 0;
let foundNewItems = true;
const scrollWaitTimeMs = 2000;
while (foundNewItems) {
const allDeleteItems = findDeleteItems();
const visibleCreations = filterVisible(allDeleteItems);
if (visibleCreations.length === 0) {
foundNewItems = false;
break;
}
console.log(`Found ${visibleCreations.length} new creations to delete.`);
for (const {card, deleteItem} of visibleCreations) {
await deleteCreative(card, deleteItem);
deletedCount++;
}
// Scroll down to load more creations
window.scrollTo(0, document.body.scrollHeight);
await sleep(scrollWaitTimeMs);
}
return {
deletedCount
};
}
bulkDeleteUnsolicitedCreations().then(
({deletedCount}) => console.log(`Deletion complete. Total items deleted: ${deletedCount}.`),
err => console.error('Error', err)
);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment