Adds a search bar to YouTube playlists. Does NOT work with shorts or when playlist video filter is set to "Shorts".
< Rückmeldungen auf YouTube Playlist Search Bar
I've updated the UI again.
// ==UserScript==
// @name YouTube Playlist Search Bar
// @namespace http://tampermonkey.net/
// @version 1.1
// @description Adds a search bar to YouTube playlists. Search by title or channel name (using @). Does NOT work with shorts or when playlist video filter is set to "Shorts".
// @match https://www.youtube.com/playlist*
// @author Setnour6
// @grant none
// @license GPL-3.0
// @downloadURL https://update.greasyfork.org/scripts/517253/YouTube%20Playlist%20Search%20Bar.user.js
// @updateURL https://update.greasyfork.org/scripts/517253/YouTube%20Playlist%20Search%20Bar.meta.js
// ==/UserScript==
(function() {
'use strict';
function createSearchBar() {
// Prevent duplicate search bars
if (document.getElementById('playlist-search-bar')) return;
// Find the playlist container element
const target = document.querySelector('#page-manager ytd-playlist-video-list-renderer');
if (!target) return;
// Create main container for the search feature
const container = document.createElement('div');
container.id = 'playlist-search-bar';
container.style.cssText = `
display: flex;
align-items: center;
margin: 24px auto 20px;
padding: 0 24px;
width: 100%;
max-width: 800px;
box-sizing: border-box;
transition: all 0.3s ease;
`;
// Create the search box container with modern styling
const searchContainer = document.createElement('div');
searchContainer.style.cssText = `
display: flex;
align-items: center;
width: 100%;
background-color: var(--yt-spec-badge-chip-background);
border-radius: 12px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
transition: all 0.3s ease;
border: 1px solid rgba(255, 255, 255, 0.1);
`;
// Create the search input field
const input = document.createElement('input');
input.id = 'playlist-search-input';
input.placeholder = 'Search in playlist...';
input.style.cssText = `
flex: 1;
padding: 14px 20px;
border: none;
border-radius: 12px 0 0 12px;
color: var(--yt-spec-text-primary);
background-color: transparent;
font-family: 'YouTube Sans', Roboto, sans-serif;
font-size: 15px;
height: 48px;
box-sizing: border-box;
outline: none;
transition: all 0.3s ease;
`;
// Create the search button
const button = document.createElement('button');
button.style.cssText = `
padding: 0;
width: 56px;
height: 48px;
border: none;
border-radius: 0 12px 12px 0;
background-color: transparent;
color: var(--yt-spec-text-primary);
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
transition: all 0.2s ease;
outline: none;
`;
// Create and style the search icon SVG
const searchIcon = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
searchIcon.setAttribute('viewBox', '0 0 24 24');
searchIcon.setAttribute('width', '22');
searchIcon.setAttribute('height', '22');
searchIcon.style.cssText = `
fill: currentColor;
opacity: 0.8;
transition: transform 0.2s ease, opacity 0.2s ease;
`;
// Add the SVG path for the search icon
const iconPath = document.createElementNS('http://www.w3.org/2000/svg', 'path');
iconPath.setAttribute('d', 'M15.5 14h-.79l-.28-.27C15.41 12.59 16 11.11 16 9.5 16 5.91 13.09 3 9.5 3S3 5.91 3 9.5 5.91 16 9.5 16c1.61 0 3.09-.59 4.23-1.57l.27.28v.79l5 4.99L20.49 19l-4.99-5zm-6 0C7.01 14 5 11.99 5 9.5S7.01 5 9.5 5 14 7.01 14 9.5 11.99 14 9.5 14z');
searchIcon.appendChild(iconPath);
button.appendChild(searchIcon);
// Add interactive effects when focusing the search input
input.addEventListener('focus', () => {
searchContainer.style.boxShadow = '0 4px 12px rgba(0, 0, 0, 0.12)';
searchContainer.style.transform = 'translateY(-1px)';
searchContainer.style.borderColor = 'rgba(255, 255, 255, 0.2)';
});
input.addEventListener('blur', () => {
searchContainer.style.boxShadow = '0 2px 8px rgba(0, 0, 0, 0.08)';
searchContainer.style.transform = 'none';
searchContainer.style.borderColor = 'rgba(255, 255, 255, 0.1)';
});
// Add hover effects for the search button
button.addEventListener('mouseover', () => {
searchIcon.style.opacity = '1';
searchIcon.style.transform = 'scale(1.1)';
});
button.addEventListener('mouseout', () => {
searchIcon.style.opacity = '0.8';
searchIcon.style.transform = 'scale(1)';
});
// Enable search on Enter key press
input.addEventListener('keydown', (event) => {
if (event.key === 'Enter') {
button.click();
}
});
// Handle search button clicks
button.addEventListener('click', () => {
const query = input.value.trim();
if (query) {
filterVideos(query);
// Add click animation
button.style.transform = 'scale(0.92)';
setTimeout(() => button.style.transform = 'scale(1)', 100);
} else {
resetFilter();
}
});
// Assemble all components and insert into the page
searchContainer.appendChild(input);
searchContainer.appendChild(button);
container.appendChild(searchContainer);
target.parentNode.insertBefore(container, target);
}
function filterVideos(query) {
// Get all video elements in the playlist
const videos = document.querySelectorAll('#contents ytd-playlist-video-renderer');
// Check if we're searching for a channel (starts with @)
const isChannelSearch = query.startsWith('@');
const searchQuery = isChannelSearch ? query.slice(1).toLowerCase() : query.toLowerCase();
// Filter videos based on search query
videos.forEach(video => {
const title = video.querySelector('#video-title').textContent.toLowerCase();
const channelName = video.querySelector('#channel-name a').textContent.toLowerCase();
let matches;
if (isChannelSearch) {
matches = channelName.includes(searchQuery);
} else {
matches = title.includes(searchQuery);
}
video.style.display = matches ? 'flex' : 'none';
});
}
function resetFilter() {
// Show all videos when search is cleared
const videos = document.querySelectorAll('#contents ytd-playlist-video-renderer');
videos.forEach(video => {
video.style.display = 'flex';
});
}
// Create a mutation observer to handle YouTube's dynamic page loading
const observer = new MutationObserver(() => {
createSearchBar();
});
observer.observe(document.body, { childList: true, subtree: true });
// Initial creation of search bar
createSearchBar();
})();
I've now updated it again so you don't need to press the search icon or press enter to search, it will do it automatically after you type something new in.
// ==UserScript==
// @name YouTube Playlist Search Bar
// @namespace http://tampermonkey.net/
// @version 1.1
// @description Adds a search bar to YouTube playlists. Search by title or channel name (using @). Does NOT work with shorts or when playlist video filter is set to "Shorts".
// @match https://www.youtube.com/playlist*
// @author Setnour6
// @grant none
// @license GPL-3.0
// @downloadURL https://update.greasyfork.org/scripts/517253/YouTube%20Playlist%20Search%20Bar.user.js
// @updateURL https://update.greasyfork.org/scripts/517253/YouTube%20Playlist%20Search%20Bar.meta.js
// ==/UserScript==
(function() {
'use strict';
function createSearchBar() {
// Prevent duplicate search bars
if (document.getElementById('playlist-search-bar')) return;
// Find the playlist container element
const target = document.querySelector('#page-manager ytd-playlist-video-list-renderer');
if (!target) return;
// Create main container for the search feature
const container = document.createElement('div');
container.id = 'playlist-search-bar';
container.style.cssText = `
display: flex;
align-items: center;
margin: 24px auto 20px;
padding: 0 24px;
width: 100%;
max-width: 800px;
box-sizing: border-box;
transition: all 0.3s ease;
`;
// Create the search box container with modern styling
const searchContainer = document.createElement('div');
searchContainer.style.cssText = `
display: flex;
align-items: center;
width: 100%;
background-color: var(--yt-spec-badge-chip-background);
border-radius: 12px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
transition: all 0.3s ease;
border: 1px solid rgba(255, 255, 255, 0.1);
`;
// Create the search input field with updated styling
const input = document.createElement('input');
input.id = 'playlist-search-input';
input.placeholder = 'Search in playlist...';
input.style.cssText = `
flex: 1;
padding: 14px 20px;
border: none;
border-radius: 12px;
color: var(--yt-spec-text-primary);
background-color: transparent;
font-family: 'YouTube Sans', Roboto, sans-serif;
font-size: 15px;
height: 48px;
box-sizing: border-box;
outline: none;
transition: all 0.3s ease;
`;
// Add interactive effects when focusing the search input
input.addEventListener('focus', () => {
searchContainer.style.boxShadow = '0 4px 12px rgba(0, 0, 0, 0.12)';
searchContainer.style.transform = 'translateY(-1px)';
searchContainer.style.borderColor = 'rgba(255, 255, 255, 0.2)';
});
input.addEventListener('blur', () => {
searchContainer.style.boxShadow = '0 2px 8px rgba(0, 0, 0, 0.08)';
searchContainer.style.transform = 'none';
searchContainer.style.borderColor = 'rgba(255, 255, 255, 0.1)';
});
// Add real-time search
input.addEventListener('input', (e) => {
const query = e.target.value.trim();
if (query) {
filterVideos(query);
} else {
resetFilter();
}
});
// Assemble components
searchContainer.appendChild(input);
container.appendChild(searchContainer);
target.parentNode.insertBefore(container, target);
}
let originalVideos = []; // Store original video elements
function filterVideos(query) {
// Get all video elements in the playlist
const videos = document.querySelectorAll('#contents ytd-playlist-video-renderer');
const playlistContents = document.querySelector('#contents.ytd-playlist-video-list-renderer');
// Store original videos if not already stored
if (originalVideos.length === 0) {
originalVideos = Array.from(videos);
}
// Check if we're searching for a channel (starts with @)
const isChannelSearch = query.startsWith('@');
const searchQuery = isChannelSearch ? query.slice(1).toLowerCase() : query.toLowerCase();
// Store matching videos
const matchingVideos = originalVideos.filter(video => {
const title = video.querySelector('#video-title').textContent.toLowerCase();
const channelName = video.querySelector('#channel-name a').textContent.toLowerCase();
return isChannelSearch ?
channelName.includes(searchQuery) :
title.includes(searchQuery);
});
// Remove all videos from the playlist
while (playlistContents.firstChild) {
playlistContents.removeChild(playlistContents.firstChild);
}
// Add only matching videos back
matchingVideos.forEach(video => {
playlistContents.appendChild(video);
});
}
function resetFilter() {
const playlistContents = document.querySelector('#contents.ytd-playlist-video-list-renderer');
// Remove current videos
while (playlistContents.firstChild) {
playlistContents.removeChild(playlistContents.firstChild);
}
// Restore original videos
originalVideos.forEach(video => {
playlistContents.appendChild(video);
});
}
// Create a mutation observer to handle YouTube's dynamic page loading
const observer = new MutationObserver(() => {
createSearchBar();
});
observer.observe(document.body, { childList: true, subtree: true });
// Initial creation of search bar
createSearchBar();
})();
Here is the code improved again with typed.js in the input box and the code has been cleaned up.
// ==UserScript==
// @name YouTube Playlist Search Bar
// @namespace http://tampermonkey.net/
// @version 1.2
// @description Adds a search bar to YouTube playlists. Search by title or channel name (using @). Does NOT work with shorts or when playlist video filter is set to "Shorts".
// @match https://www.youtube.com/playlist*
// @author Setnour6
// @grant none
// @license GPL-3.0
// @downloadURL https://update.greasyfork.org/scripts/517253/YouTube%20Playlist%20Search%20Bar.user.js
// @updateURL https://update.greasyfork.org/scripts/517253/YouTube%20Playlist%20Search%20Bar.meta.js
// ==/UserScript==
(function() {
'use strict';
// Array of placeholder texts that will be animated in the search input
const PLACEHOLDERS = [
'Search in playlist...',
'Type @ to search by channel...',
'Find your favorite videos...',
'Search through your collection...',
'Looking for something specific?',
'Filter playlist content...'
];
// CSS styles for the search bar components
const STYLES = {
container: `
display: flex;
align-items: center;
margin: 24px auto 20px;
padding: 0 24px;
width: 100%;
max-width: 800px;
box-sizing: border-box;
transition: all 0.3s ease;
`,
searchContainer: `
display: flex;
align-items: center;
width: 100%;
background-color: var(--yt-spec-badge-chip-background);
border-radius: 12px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
transition: all 0.3s ease;
border: 1px solid rgba(255, 255, 255, 0.1);
`,
input: `
flex: 1;
padding: 14px 20px;
border: none;
border-radius: 12px;
color: var(--yt-spec-text-primary);
background-color: transparent;
font-family: 'YouTube Sans', Roboto, sans-serif;
font-size: 15px;
height: 48px;
box-sizing: border-box;
outline: none;
transition: all 0.3s ease;
`
};
// Global variables to track state
let originalVideos = []; // Stores the original list of videos before filtering
let isTyping = true; // Controls typing animation direction (typing vs deleting)
let isAnimating = false; // Prevents multiple animations from running simultaneously
let currentPlaceholderIndex = 0; // Tracks which placeholder text is currently being shown
// Handles the animated typing effect for placeholder text
async function animatePlaceholder(input) {
if (isAnimating) return;
isAnimating = true;
while (document.getElementById('playlist-search-input') && !input.value && !input.matches(':focus')) {
const placeholder = PLACEHOLDERS[currentPlaceholderIndex];
if (isTyping) {
// Typing effect
input.placeholder = ''; // Clear before starting
for (let i = 0; i <= placeholder.length; i++) {
if (!document.getElementById('playlist-search-input') || input.value || input.matches(':focus')) {
isAnimating = false;
return;
}
input.placeholder = placeholder.slice(0, i);
await new Promise(resolve => setTimeout(resolve, 50));
}
await new Promise(resolve => setTimeout(resolve, 2000));
isTyping = false;
} else {
// Deleting effect
for (let i = placeholder.length; i >= 0; i--) {
if (!document.getElementById('playlist-search-input') || input.value || input.matches(':focus')) {
isAnimating = false;
return;
}
input.placeholder = placeholder.slice(0, i);
await new Promise(resolve => setTimeout(resolve, 30));
}
currentPlaceholderIndex = (currentPlaceholderIndex + 1) % PLACEHOLDERS.length;
isTyping = true;
await new Promise(resolve => setTimeout(resolve, 500));
}
}
isAnimating = false;
}
// Handles visual changes when the search input is focused
function handleFocus(searchContainer, input) {
searchContainer.style.boxShadow = '0 4px 12px rgba(0, 0, 0, 0.12)';
searchContainer.style.transform = 'translateY(-1px)';
searchContainer.style.borderColor = 'rgba(255, 255, 255, 0.2)';
input.placeholder = '';
}
// Handles visual changes when the search input loses focus
function handleBlur(searchContainer, input) {
searchContainer.style.boxShadow = '0 2px 8px rgba(0, 0, 0, 0.08)';
searchContainer.style.transform = 'none';
searchContainer.style.borderColor = 'rgba(255, 255, 255, 0.1)';
// Only restart animation if the input is empty
if (!input.value) {
// Reset all animation states
isAnimating = false;
isTyping = true;
currentPlaceholderIndex = 0;
input.placeholder = '';
setTimeout(() => {
if (!input.value && !input.matches(':focus')) {
animatePlaceholder(input);
}
}, 500); // Delay restart of animation
}
}
// Filters the playlist videos based on search query
function filterVideos(query) {
const playlistContents = document.querySelector('#contents.ytd-playlist-video-list-renderer');
if (!playlistContents) return;
// Store original videos on first search
if (originalVideos.length === 0) {
originalVideos = Array.from(document.querySelectorAll('#contents ytd-playlist-video-renderer'));
}
// Check if searching by channel name (using @) or video title
const isChannelSearch = query.startsWith('@');
const searchQuery = isChannelSearch ? query.slice(1).toLowerCase() : query.toLowerCase();
// Filter videos based on search criteria
const matchingVideos = originalVideos.filter(video => {
const title = video.querySelector('#video-title').textContent.toLowerCase();
const channelName = video.querySelector('#channel-name a').textContent.toLowerCase();
return isChannelSearch ? channelName.includes(searchQuery) : title.includes(searchQuery);
});
updatePlaylistContents(playlistContents, matchingVideos);
}
// Resets the playlist to show all videos
function resetFilter() {
const playlistContents = document.querySelector('#contents.ytd-playlist-video-list-renderer');
if (!playlistContents) return;
updatePlaylistContents(playlistContents, originalVideos);
}
// Updates the playlist container with filtered videos
function updatePlaylistContents(container, videos) {
container.replaceChildren(...videos);
}
// Creates and injects the search bar into the YouTube playlist page
function createSearchBar() {
// Prevent duplicate search bars
if (document.getElementById('playlist-search-bar')) return;
const target = document.querySelector('#page-manager ytd-playlist-video-list-renderer');
if (!target) return;
const container = document.createElement('div');
container.id = 'playlist-search-bar';
container.style.cssText = STYLES.container;
const searchContainer = document.createElement('div');
searchContainer.style.cssText = STYLES.searchContainer;
const input = document.createElement('input');
input.id = 'playlist-search-input';
input.placeholder = '';
input.style.cssText = STYLES.input;
input.addEventListener('focus', () => handleFocus(searchContainer, input));
input.addEventListener('blur', () => handleBlur(searchContainer, input));
input.addEventListener('input', (e) => {
const query = e.target.value.trim();
query ? filterVideos(query) : resetFilter();
});
searchContainer.appendChild(input);
container.appendChild(searchContainer);
target.parentNode.insertBefore(container, target);
requestAnimationFrame(() => animatePlaceholder(input));
}
// Initialize the script
// Use MutationObserver to handle YouTube's dynamic page loading
const observer = new MutationObserver(createSearchBar);
observer.observe(document.body, { childList: true, subtree: true });
createSearchBar();
})();
Sorry for all the replies but I'm just doing them as Im adding stuff😂 This time I've made it so the search works smoother and is not as stuttery.
// ==UserScript==
// @name YouTube Playlist Search Bar
// @namespace http://tampermonkey.net/
// @version 1.2
// @description Adds a search bar to YouTube playlists. Search by title or channel name (using @). Does NOT work with shorts or when playlist video filter is set to "Shorts".
// @match https://www.youtube.com/playlist*
// @author Setnour6
// @grant none
// @license GPL-3.0
// @downloadURL https://update.greasyfork.org/scripts/517253/YouTube%20Playlist%20Search%20Bar.user.js
// @updateURL https://update.greasyfork.org/scripts/517253/YouTube%20Playlist%20Search%20Bar.meta.js
// ==/UserScript==
(function() {
'use strict';
// Array of placeholder texts that will be animated in the search input
const PLACEHOLDERS = [
'Search in playlist...',
'Type @ to search by channel...',
'Find your favorite videos...',
'Search through your collection...',
'Looking for something specific?',
'Filter playlist content...'
];
// CSS styles for the search bar components
const STYLES = {
container: `
display: flex;
align-items: center;
margin: 24px auto 20px;
padding: 0 24px;
width: 100%;
max-width: 800px;
box-sizing: border-box;
transition: all 0.3s ease;
`,
searchContainer: `
display: flex;
align-items: center;
width: 100%;
background-color: var(--yt-spec-badge-chip-background);
border-radius: 12px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
transition: all 0.3s ease;
border: 1px solid rgba(255, 255, 255, 0.1);
`,
input: `
flex: 1;
padding: 14px 20px;
border: none;
border-radius: 12px;
color: var(--yt-spec-text-primary);
background-color: transparent;
font-family: 'YouTube Sans', Roboto, sans-serif;
font-size: 15px;
height: 48px;
box-sizing: border-box;
outline: none;
transition: all 0.3s ease;
`
};
// Global variables to track state
let originalVideos = []; // Stores the original list of videos before filtering
let isTyping = true; // Controls typing animation direction (typing vs deleting)
let isAnimating = false; // Prevents multiple animations from running simultaneously
let currentPlaceholderIndex = 0; // Tracks which placeholder text is currently being shown
// Handles the animated typing effect for placeholder text
async function animatePlaceholder(input) {
if (isAnimating) return;
isAnimating = true;
while (document.getElementById('playlist-search-input') && !input.value && !input.matches(':focus')) {
const placeholder = PLACEHOLDERS[currentPlaceholderIndex];
if (isTyping) {
// Typing effect
input.placeholder = ''; // Clear before starting
for (let i = 0; i <= placeholder.length; i++) {
if (!document.getElementById('playlist-search-input') || input.value || input.matches(':focus')) {
isAnimating = false;
return;
}
input.placeholder = placeholder.slice(0, i);
await new Promise(resolve => setTimeout(resolve, 50));
}
await new Promise(resolve => setTimeout(resolve, 2000));
isTyping = false;
} else {
// Deleting effect
for (let i = placeholder.length; i >= 0; i--) {
if (!document.getElementById('playlist-search-input') || input.value || input.matches(':focus')) {
isAnimating = false;
return;
}
input.placeholder = placeholder.slice(0, i);
await new Promise(resolve => setTimeout(resolve, 30));
}
currentPlaceholderIndex = (currentPlaceholderIndex + 1) % PLACEHOLDERS.length;
isTyping = true;
await new Promise(resolve => setTimeout(resolve, 500));
}
}
isAnimating = false;
}
// Handles visual changes when the search input is focused
function handleFocus(searchContainer, input) {
searchContainer.style.boxShadow = '0 4px 12px rgba(0, 0, 0, 0.12)';
searchContainer.style.transform = 'translateY(-1px)';
searchContainer.style.borderColor = 'rgba(255, 255, 255, 0.2)';
input.placeholder = '';
}
// Handles visual changes when the search input loses focus
function handleBlur(searchContainer, input) {
searchContainer.style.boxShadow = '0 2px 8px rgba(0, 0, 0, 0.08)';
searchContainer.style.transform = 'none';
searchContainer.style.borderColor = 'rgba(255, 255, 255, 0.1)';
// Only restart animation if the input is empty
if (!input.value) {
// Reset all animation states
isAnimating = false;
isTyping = true;
currentPlaceholderIndex = 0;
input.placeholder = '';
setTimeout(() => {
if (!input.value && !input.matches(':focus')) {
animatePlaceholder(input);
}
}, 500); // Delay restart of animation
}
}
// Filters the playlist videos based on search query
function filterVideos(query) {
const playlistContents = document.querySelector('#contents.ytd-playlist-video-list-renderer');
if (!playlistContents) return;
// Store original videos on first search
if (originalVideos.length === 0) {
originalVideos = Array.from(document.querySelectorAll('#contents ytd-playlist-video-renderer'));
}
// Check if searching by channel name (using @) or video title
const isChannelSearch = query.startsWith('@');
const searchQuery = isChannelSearch ? query.slice(1).toLowerCase() : query.toLowerCase();
// Filter videos based on search criteria
const matchingVideos = originalVideos.filter(video => {
const title = video.querySelector('#video-title').textContent.toLowerCase();
const channelName = video.querySelector('#channel-name a').textContent.toLowerCase();
return isChannelSearch ? channelName.includes(searchQuery) : title.includes(searchQuery);
});
updatePlaylistContents(playlistContents, matchingVideos);
}
// Resets the playlist to show all videos
function resetFilter() {
const playlistContents = document.querySelector('#contents.ytd-playlist-video-list-renderer');
if (!playlistContents) return;
updatePlaylistContents(playlistContents, originalVideos);
}
// Updates the playlist container with filtered videos
function updatePlaylistContents(container, videos) {
// Clear the container first
container.textContent = '';
// Process videos in batches
const BATCH_SIZE = 10;
let currentIndex = 0;
function processBatch() {
const batch = videos.slice(currentIndex, currentIndex + BATCH_SIZE);
if (batch.length === 0) return;
requestAnimationFrame(() => {
batch.forEach(video => container.appendChild(video));
currentIndex += BATCH_SIZE;
// Schedule next batch
if (currentIndex < videos.length) {
setTimeout(processBatch, 16); // Roughly aims for 60fps
}
});
}
processBatch();
}
// Creates and injects the search bar into the YouTube playlist page
function createSearchBar() {
// Prevent duplicate search bars
if (document.getElementById('playlist-search-bar')) return;
const target = document.querySelector('#page-manager ytd-playlist-video-list-renderer');
if (!target) return;
const container = document.createElement('div');
container.id = 'playlist-search-bar';
container.style.cssText = STYLES.container;
const searchContainer = document.createElement('div');
searchContainer.style.cssText = STYLES.searchContainer;
const input = document.createElement('input');
input.id = 'playlist-search-input';
input.placeholder = '';
input.style.cssText = STYLES.input;
input.addEventListener('focus', () => handleFocus(searchContainer, input));
input.addEventListener('blur', () => handleBlur(searchContainer, input));
input.addEventListener('input', (e) => {
const query = e.target.value.trim();
query ? filterVideos(query) : resetFilter();
});
searchContainer.appendChild(input);
container.appendChild(searchContainer);
target.parentNode.insertBefore(container, target);
requestAnimationFrame(() => animatePlaceholder(input));
}
// Initialize the script
// Use MutationObserver to handle YouTube's dynamic page loading
const observer = new MutationObserver(createSearchBar);
observer.observe(document.body, { childList: true, subtree: true });
createSearchBar();
})();
Thanks for providing this script, it works great :) Also I did some tinkering to your script and added a feature where if you do @ and then the youtube channels name only videos you've liked from that channel will pop up and I improved the UI a bit.
// ==UserScript==
// @name YouTube Playlist Search Bar
// @namespace http://tampermonkey.net/
// @version 1.1
// @description Adds a search bar to YouTube playlists. Search by title or channel name (using @). Does NOT work with shorts or when playlist video filter is set to "Shorts".
// @match https://www.youtube.com/playlist*
// @author Setnour6
// @grant none
// @license GPL-3.0
// @downloadURL https://update.greasyfork.org/scripts/517253/YouTube%20Playlist%20Search%20Bar.user.js
// @updateURL https://update.greasyfork.org/scripts/517253/YouTube%20Playlist%20Search%20Bar.meta.js
// ==/UserScript==
(function() {
'use strict';
function createSearchBar() {
// Check if custom search bar already exists
if (document.getElementById('playlist-search-bar')) return;
// Find target element to place search bar under sorting options
const target = document.querySelector('#page-manager ytd-playlist-video-list-renderer');
// Only proceed if target element exists
if (!target) return;
const container = document.createElement('div');
container.id = 'playlist-search-bar';
container.style.display = 'flex';
container.style.alignItems = 'center';
container.style.marginBottom = '16px';
container.style.padding = '8px 16px';
container.style.width = '93%';
container.style.marginLeft = 'auto';
container.style.marginRight = 'auto';
container.style.transition = 'all 0.2s ease';
const searchContainer = document.createElement('div');
searchContainer.style.display = 'flex';
searchContainer.style.alignItems = 'center';
searchContainer.style.width = '100%';
searchContainer.style.maxWidth = '600px';
searchContainer.style.margin = '0 auto';
searchContainer.style.backgroundColor = 'var(--yt-spec-badge-chip-background)';
searchContainer.style.borderRadius = '24px';
searchContainer.style.boxShadow = '0 2px 5px rgba(0, 0, 0, 0.1)';
searchContainer.style.transition = 'all 0.2s ease';
const input = document.createElement('input');
input.id = 'playlist-search-input';
input.placeholder = 'Filter videos by title or channel (@channel)...';
input.style.flex = '1';
input.style.padding = '12px 16px';
input.style.border = 'none';
input.style.borderRadius = '24px 0 0 24px';
input.style.color = 'var(--yt-spec-text-primary)';
input.style.backgroundColor = 'transparent';
input.style.fontFamily = 'Roboto, sans-serif';
input.style.fontSize = '14px';
input.style.height = '40px';
input.style.boxSizing = 'border-box';
input.style.outline = 'none';
input.style.transition = 'all 0.2s ease';
input.addEventListener('focus', () => {
searchContainer.style.boxShadow = '0 4px 10px rgba(0, 0, 0, 0.15)';
searchContainer.style.transform = 'translateY(-1px)';
});
input.addEventListener('blur', () => {
searchContainer.style.boxShadow = '0 2px 5px rgba(0, 0, 0, 0.1)';
searchContainer.style.transform = 'none';
});
input.addEventListener('keydown', (event) => {
if (event.key === 'Enter') {
button.click();
}
});
const button = document.createElement('button');
button.style.padding = '0';
button.style.width = '48px';
button.style.height = '40px';
button.style.border = 'none';
button.style.borderRadius = '0 24px 24px 0';
button.style.backgroundColor = '#FF0000';
button.style.color = '#fff';
button.style.cursor = 'pointer';
button.style.display = 'flex';
button.style.alignItems = 'center';
button.style.justifyContent = 'center';
button.style.transition = 'all 0.2s ease';
button.style.outline = 'none';
// Create search icon SVG
const searchIcon = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
searchIcon.setAttribute('viewBox', '0 0 24 24');
searchIcon.setAttribute('width', '20');
searchIcon.setAttribute('height', '20');
searchIcon.style.fill = 'currentColor';
searchIcon.style.transition = 'transform 0.2s ease';
const iconPath = document.createElementNS('http://www.w3.org/2000/svg', 'path');
iconPath.setAttribute('d', 'M15.5 14h-.79l-.28-.27C15.41 12.59 16 11.11 16 9.5 16 5.91 13.09 3 9.5 3S3 5.91 3 9.5 5.91 16 9.5 16c1.61 0 3.09-.59 4.23-1.57l.27.28v.79l5 4.99L20.49 19l-4.99-5zm-6 0C7.01 14 5 11.99 5 9.5S7.01 5 9.5 5 14 7.01 14 9.5 11.99 14 9.5 14z');
searchIcon.appendChild(iconPath);
button.appendChild(searchIcon);
// Enhanced hover effects
button.addEventListener('mouseover', () => {
button.style.backgroundColor = '#CC0000';
searchIcon.style.transform = 'scale(1.1)';
});
button.addEventListener('mouseout', () => {
button.style.backgroundColor = '#FF0000';
searchIcon.style.transform = 'scale(1)';
});
button.addEventListener('click', () => {
const query = input.value.trim();
if (query) {
filterVideos(query);
button.style.transform = 'scale(0.95)';
setTimeout(() => {
button.style.transform = 'scale(1)';
}, 100);
} else {
resetFilter();
}
});
searchContainer.appendChild(input);
searchContainer.appendChild(button);
container.appendChild(searchContainer);
target.parentNode.insertBefore(container, target);
}
function filterVideos(query) {
const videos = document.querySelectorAll('#contents ytd-playlist-video-renderer');
// Check if searching for channel
const isChannelSearch = query.startsWith('@');
const searchQuery = isChannelSearch ? query.slice(1).toLowerCase() : query.toLowerCase();
videos.forEach(video => {
const title = video.querySelector('#video-title').textContent.toLowerCase();
const channelName = video.querySelector('#channel-name a').textContent.toLowerCase();
let matches;
if (isChannelSearch) {
matches = channelName.includes(searchQuery);
} else {
matches = title.includes(searchQuery);
}
video.style.display = matches ? 'flex' : 'none';
});
}
function resetFilter() {
const videos = document.querySelectorAll('#contents ytd-playlist-video-renderer');
videos.forEach(video => {
video.style.display = 'flex';
});
}
const observer = new MutationObserver(() => {
createSearchBar();
});
observer.observe(document.body, { childList: true, subtree: true });
createSearchBar();
})();