Skip to content

Instantly share code, notes, and snippets.

@koenighotze
Created October 29, 2025 08:28
Show Gist options
  • Select an option

  • Save koenighotze/89e2a69b5cba093c8f297f1dc228a6c8 to your computer and use it in GitHub Desktop.

Select an option

Save koenighotze/89e2a69b5cba093c8f297f1dc228a6c8 to your computer and use it in GitHub Desktop.
Remove videos from YT playlist
(async () => {
// === CONFIG ===
const COUNT = 500; // how many from the top to remove
const PER_ITEM_TIMEOUT = 8000; // ms to wait for menu items per video
const BETWEEN_CLICKS_DELAY = 400; // ms delay between actions
// === UTILITIES ===
const sleep = (ms) => new Promise(res => setTimeout(res, ms));
const waitFor = async (predicate, {timeout = 5000, interval = 100} = {}) => {
const start = performance.now();
let result;
while (performance.now() - start < timeout) {
result = predicate();
if (result) return result;
await sleep(interval);
}
return null;
};
// Matchers for the "Remove from Watch later" menu item (EN + basic DE).
const isRemoveItem = (el) => {
const t = (el.textContent || '').trim().toLowerCase();
return (
t.includes('remove from watch later') ||
t.includes('aus "später ansehen" entfernen') ||
t.includes('aus „später ansehen“ entfernen') || // German curly quotes
t.includes('aus später ansehen entfernen')
);
};
// Ensure we are on the WL playlist page
if (!/\/playlist\?/.test(location.pathname + location.search) || !/[\?&]list=WL\b/.test(location.search)) {
console.warn('Please open https://www.youtube.com/playlist?list=WL and run again.');
return;
}
// Helper to (re)collect visible playlist items
const getItems = () => Array.from(document.querySelectorAll('ytd-playlist-video-renderer'));
// Scroll enough to load at least COUNT items
const ensureItemsLoaded = async (count) => {
let tries = 0;
while (getItems().length < count && tries < 50) {
window.scrollBy(0, 1200);
await sleep(200);
tries++;
}
};
await ensureItemsLoaded(COUNT);
const items = getItems().slice(0, COUNT);
if (items.length === 0) {
console.warn('No videos found. Ensure the list is visible and not empty.');
return;
}
console.log(`Attempting to remove ${items.length} item(s) from the top of Watch later...`);
let removed = 0, failed = 0;
for (let idx = 0; idx < items.length; idx++) {
const item = items[idx];
// Bring the item into view to help YouTube attach the proper menu
item.scrollIntoView({block: 'center'});
await sleep(150);
try {
// 1) Open the per-item action menu
// Most stable: the first button inside the #menu area of the item.
let menuBtn =
item.querySelector('#menu button') ||
item.querySelector('ytd-menu-renderer #button') ||
item.querySelector('button[aria-haspopup="true"]');
if (!menuBtn) {
throw new Error('Menu button not found');
}
menuBtn.click();
// 2) Wait for the menu panel and find the "Remove from Watch later" entry
const menuItem = await waitFor(() => {
// YouTube uses ytd-menu-service-item-renderer for items in the popup menu
const candidates = Array.from(document.querySelectorAll('ytd-menu-service-item-renderer, tp-yt-paper-item, ytd-compact-link-renderer'));
return candidates.find(isRemoveItem);
}, { timeout: PER_ITEM_TIMEOUT });
if (!menuItem) {
// Try closing any open menu so subsequent iterations are clean
document.body.click();
throw new Error('Remove menu item not found (UI/locale may differ).');
}
// 3) Click the remove action
(menuItem.querySelector('a, tp-yt-paper-item, yt-formatted-string, button') || menuItem).click();
removed++;
console.log(`Removed #${idx + 1} ✓`);
await sleep(BETWEEN_CLICKS_DELAY);
} catch (e) {
failed++;
console.warn(`Failed on item #${idx + 1}: ${e.message}`);
// Try to close any stuck menu/snackbar
document.body.click();
await sleep(200);
}
}
console.log(`Done. Removed: ${removed}, Failed: ${failed}.`);
// Optional: refresh the page to reflect changes immediately
// location.reload();
})();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment