Skip to content

Instantly share code, notes, and snippets.

@rezarajan
Created April 2, 2026 03:18
Show Gist options
  • Select an option

  • Save rezarajan/77b66de37d708158569f4fbdf4ef6bbc to your computer and use it in GitHub Desktop.

Select an option

Save rezarajan/77b66de37d708158569f4fbdf4ef6bbc to your computer and use it in GitHub Desktop.
A script which automated the deletion of all photos in Google Photos. Simply copy-and-paste into the developer console.
(async function () {
"use strict";
const CONFIG = {
maxRuntimeMinutes: 30, // safety cutoff
maxPerBatch: 30,
stallLimit: 5,
scrollDelay: 1200,
actionDelay: 1200,
dryRun: false,
};
const startTime = Date.now();
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
function log(msg, type = "info") {
const styles = {
info: "color:#2196F3",
success: "color:#4CAF50",
warn: "color:#FFC107",
error: "color:#F44336",
};
console.log(`%c[AUTO-DELETE] ${msg}`, styles[type]);
}
function getCheckboxes() {
return Array.from(
document.querySelectorAll(".ckGgle[aria-checked='false']")
).filter((el) => el.offsetParent !== null);
}
function getDeleteButton() {
return document.querySelector('button[aria-label="Move to trash"]');
}
function getConfirmButton() {
return Array.from(document.querySelectorAll("button")).find(
(b) =>
b.textContent.trim() === "Move to trash" &&
b.offsetParent !== null
);
}
async function autoScroll() {
const before = document.body.scrollHeight;
window.scrollTo(0, before);
await sleep(CONFIG.scrollDelay);
const after = document.body.scrollHeight;
return after > before; // did new content load?
}
async function deleteBatch() {
const boxes = getCheckboxes().slice(0, CONFIG.maxPerBatch);
if (!boxes.length) return 0;
log(`Selecting ${boxes.length} items...`);
boxes.forEach((b) => {
try { b.click(); } catch {}
});
await sleep(400);
if (CONFIG.dryRun) {
log("DRY RUN: skipping delete", "warn");
return boxes.length;
}
const deleteBtn = getDeleteButton();
if (!deleteBtn) {
log("Delete button not found", "error");
return 0;
}
deleteBtn.click();
await sleep(500);
const confirmBtn = getConfirmButton();
if (!confirmBtn) {
log("Confirm button not found", "error");
return 0;
}
confirmBtn.click();
await sleep(CONFIG.actionDelay);
return boxes.length;
}
async function run() {
let totalDeleted = 0;
let stallCounter = 0;
let noNewScrolls = 0;
log("=== FULL AUTO MODE STARTED ===", "success");
while (true) {
// Safety timeout
if ((Date.now() - startTime) > CONFIG.maxRuntimeMinutes * 60000) {
log("Max runtime reached. Stopping.", "warn");
break;
}
// Step 1: Scroll to load more
const newLoaded = await autoScroll();
if (!newLoaded) {
noNewScrolls++;
log(`No new content loaded (${noNewScrolls})`, "warn");
} else {
noNewScrolls = 0;
}
// Step 2: Try deleting visible batch
const deleted = await deleteBatch();
if (deleted === 0) {
stallCounter++;
log(`No deletions this cycle (${stallCounter})`, "warn");
} else {
stallCounter = 0;
totalDeleted += deleted;
log(`Deleted ${deleted}. Total: ${totalDeleted}`, "success");
}
// Step 3: Exit conditions
if (stallCounter >= CONFIG.stallLimit && noNewScrolls >= 3) {
log("No progress + no new content. Likely finished.", "success");
break;
}
// Step 4: Reset view occasionally (prevents UI bugs)
window.scrollTo(0, 0);
await sleep(800);
}
log(`DONE. Total deleted: ~${totalDeleted}`, "success");
}
await run();
})();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment