Created
November 27, 2025 19:57
-
-
Save milichev/bca2f30d5339c39ea898b1f8f87e6612 to your computer and use it in GitHub Desktop.
Delete all unsaved creations in Google Photos
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| /** | |
| * @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