Awesome-claude-skills tampermonkey-developer
Comprehensive toolkit for developing, testing, and debugging Tampermonkey userscripts. Supports DOM manipulation, API integration, XHR/fetch interception, styling injection, and modern JavaScript patterns. Use for building browser automation scripts, web page enhancements, and custom browser extensions.
git clone https://github.com/nero2465/awesome-claude-skills
T=$(mktemp -d) && git clone --depth=1 https://github.com/nero2465/awesome-claude-skills "$T" && mkdir -p ~/.claude/skills && cp -r "$T/tampermonkey-developer" ~/.claude/skills/nero2465-awesome-claude-skills-tampermonkey-developer && rm -rf "$T"
tampermonkey-developer/SKILL.mdTampermonkey Developer
A comprehensive skill for developing professional Tampermonkey userscripts with modern JavaScript patterns, best practices, and debugging capabilities.
Quick Start
Step 1: Understand the Requirements
Before building a userscript:
- Identify target website(s) and URL patterns
- Determine what DOM elements need manipulation
- Understand timing requirements (DOMContentLoaded vs window.onload)
- Check for SPA (Single Page Application) requirements
Step 2: Generate Userscript Structure
Use the template generator to create a new userscript:
bash scripts/generate-userscript.sh <script-name> <target-url>
This creates a fully structured userscript with:
- ✅ Complete metadata block with proper @grant declarations
- ✅ Modern ES6+ JavaScript structure
- ✅ Mutation observer for dynamic content handling
- ✅ Error handling and logging framework
- ✅ Common utility functions
Step 3: Develop the Userscript
Edit the generated
.user.js file following these principles:
DOM Manipulation Best Practices:
- Always wait for DOM ready state
- Use
for dynamic contentMutationObserver - Prefer
/querySelector
over jQueryquerySelectorAll - Cache DOM references for performance
- Use
for visual changesrequestAnimationFrame
Timing Considerations:
- Before any DOM elements exist@run-at document-start
- When@run-at document-body
exists (default)<body>
- After DOM is loaded (like DOMContentLoaded)@run-at document-end
- After page fully loaded (like window.onload)@run-at document-idle
Security & Permissions: Only request grants you actually need:
/GM_setValue
- Persistent storageGM_getValue
- Cross-origin requestsGM_xmlhttpRequest
- Inject CSSGM_addStyle
- Clipboard accessGM_setClipboard
- Browser notificationsGM_notification
- Open new tabsGM_openInTab
- Access page's window object (use with caution)unsafeWindow
Step 4: Testing & Debugging
Local Testing:
- Install in Tampermonkey
- Enable debug mode in script settings
- Use
with script prefix for filteringconsole.log - Monitor with browser DevTools
Common Debugging Commands:
// Script info logging console.log('[YourScript]', 'Initialized'); // DOM element checking console.assert(element, 'Element not found!'); // Performance monitoring console.time('operation'); // ... your code console.timeEnd('operation');
Anti-Pattern Detection: Use the validator script to check for common issues:
bash scripts/validate-userscript.sh your-script.user.js
Checks for:
- Missing @grant declarations
- Inefficient selectors
- Memory leaks (unremoved event listeners)
- CSP violations
- Insecure patterns (eval, innerHTML with user input)
Step 5: Optimization
Performance Tips:
- Debounce/throttle event handlers
- Use event delegation for dynamic elements
- Minimize DOM queries (cache results)
- Avoid synchronous XHR
- Use
instead of inline stylesGM_addStyle
Example - Debounce Function:
function debounce(func, wait) { let timeout; return function executedFunction(...args) { clearTimeout(timeout); timeout = setTimeout(() => func.apply(this, args), wait); }; } // Usage const handleScroll = debounce(() => { console.log('Scrolled!'); }, 250); window.addEventListener('scroll', handleScroll);
Common Patterns
Pattern 1: Waiting for Elements
// Using Mutation Observer (recommended for dynamic content) function waitForElement(selector, callback, timeout = 10000) { const startTime = Date.now(); // Check if element already exists const element = document.querySelector(selector); if (element) return callback(element); // Setup observer const observer = new MutationObserver((mutations, obs) => { const element = document.querySelector(selector); if (element) { obs.disconnect(); callback(element); } else if (Date.now() - startTime > timeout) { obs.disconnect(); console.warn(`Timeout waiting for: ${selector}`); } }); observer.observe(document.body, { childList: true, subtree: true }); } // Usage waitForElement('.target-class', (element) => { element.style.backgroundColor = 'yellow'; });
Pattern 2: Cross-Origin API Requests
// Using GM_xmlhttpRequest for CORS-free requests function fetchAPI(url, options = {}) { return new Promise((resolve, reject) => { GM_xmlhttpRequest({ method: options.method || 'GET', url: url, headers: options.headers || {}, data: options.body, onload: (response) => { if (response.status >= 200 && response.status < 300) { try { resolve(JSON.parse(response.responseText)); } catch { resolve(response.responseText); } } else { reject(new Error(`HTTP ${response.status}`)); } }, onerror: (error) => reject(error), ontimeout: () => reject(new Error('Request timeout')) }); }); } // Usage fetchAPI('https://api.example.com/data') .then(data => console.log(data)) .catch(err => console.error('API Error:', err));
Pattern 3: Persistent Settings
// Settings manager with defaults class ScriptSettings { constructor(defaults = {}) { this.defaults = defaults; } get(key) { const value = GM_getValue(key); return value !== undefined ? value : this.defaults[key]; } set(key, value) { GM_setValue(key, value); } getAll() { const settings = {}; for (const key in this.defaults) { settings[key] = this.get(key); } return settings; } } // Usage const settings = new ScriptSettings({ enabled: true, theme: 'dark', maxItems: 50 }); if (settings.get('enabled')) { // Run script logic }
Pattern 4: Style Injection
// Inject CSS with GM_addStyle (CSP-safe) GM_addStyle(` .custom-element { background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); padding: 12px 24px; border-radius: 8px; color: white; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); transition: transform 0.2s; } .custom-element:hover { transform: translateY(-2px); box-shadow: 0 6px 8px rgba(0, 0, 0, 0.15); } `);
Pattern 5: SPA Navigation Detection
// Detect route changes in Single Page Applications let lastUrl = location.href; function onLocationChange(callback) { new MutationObserver(() => { const currentUrl = location.href; if (currentUrl !== lastUrl) { lastUrl = currentUrl; callback(currentUrl); } }).observe(document, { subtree: true, childList: true }); } // Usage onLocationChange((url) => { console.log('Navigation detected:', url); // Re-initialize script logic for new page initializeScript(); });
Advanced Techniques
Intercepting XHR/Fetch Requests
// Intercept and modify fetch requests (function(fetch) { window.fetch = function(...args) { console.log('Fetch intercepted:', args[0]); // Modify request if needed if (args[0].includes('api.example.com')) { const modifiedInit = args[1] || {}; modifiedInit.headers = { ...modifiedInit.headers, 'X-Custom-Header': 'MyValue' }; args[1] = modifiedInit; } return fetch.apply(this, args) .then(response => { console.log('Fetch response:', response.url, response.status); return response; }); }; })(window.fetch);
Creating Draggable UI Panels
function createDraggablePanel(content) { const panel = document.createElement('div'); panel.innerHTML = ` <div class="panel-header" style="cursor: move; background: #333; color: white; padding: 10px;"> Drag Me <button class="close-btn" style="float: right;">×</button> </div> <div class="panel-content" style="background: white; padding: 15px;"> ${content} </div> `; panel.style.cssText = 'position: fixed; top: 50px; right: 50px; width: 300px; border-radius: 8px; box-shadow: 0 4px 12px rgba(0,0,0,0.3); z-index: 99999;'; // Make draggable const header = panel.querySelector('.panel-header'); let isDragging = false; let currentX, currentY, initialX, initialY; header.addEventListener('mousedown', (e) => { isDragging = true; initialX = e.clientX - panel.offsetLeft; initialY = e.clientY - panel.offsetTop; }); document.addEventListener('mousemove', (e) => { if (isDragging) { e.preventDefault(); currentX = e.clientX - initialX; currentY = e.clientY - initialY; panel.style.left = currentX + 'px'; panel.style.top = currentY + 'px'; panel.style.right = 'auto'; } }); document.addEventListener('mouseup', () => isDragging = false); // Close button panel.querySelector('.close-btn').addEventListener('click', () => panel.remove()); document.body.appendChild(panel); return panel; } // Usage createDraggablePanel('<h3>Settings</h3><p>Your content here</p>');
Keyboard Shortcuts
// Register keyboard shortcuts function registerHotkey(keys, callback) { const keysArray = keys.toLowerCase().split('+').map(k => k.trim()); document.addEventListener('keydown', (e) => { const pressed = []; if (e.ctrlKey) pressed.push('ctrl'); if (e.altKey) pressed.push('alt'); if (e.shiftKey) pressed.push('shift'); if (e.metaKey) pressed.push('meta'); pressed.push(e.key.toLowerCase()); if (pressed.sort().join('+') === keysArray.sort().join('+')) { e.preventDefault(); callback(e); } }); } // Usage registerHotkey('ctrl+shift+x', () => { console.log('Hotkey triggered!'); // Your action here });
Troubleshooting
Script Not Running
- Check @match or @include patterns match current URL
- Verify @run-at timing is appropriate
- Check console for JavaScript errors
- Ensure Tampermonkey is enabled for the site
- Try incognito mode to rule out conflicts
Element Not Found
- Check if page is SPA (needs navigation detection)
- Increase wait timeout
- Verify selector is correct (use DevTools)
- Check if element is in iframe
- Use MutationObserver instead of fixed delays
Performance Issues
- Remove unused @grant declarations
- Debounce/throttle event handlers
- Cache DOM queries
- Use event delegation
- Check for memory leaks (console → Memory profiler)
CSP (Content Security Policy) Errors
- Use
instead of inline stylesGM_addStyle - Use
if accessing page context@grant unsafeWindow - Avoid inline event handlers (use addEventListener)
- Use
for external requestsGM_xmlhttpRequest
Best Practices Summary
- Always declare @grant - Even if using
none - Use MutationObserver - For dynamic content
- Cache DOM references - Query once, use many times
- Handle errors gracefully - Wrap in try-catch blocks
- Prefix console logs - Easy filtering in DevTools
- Version your scripts - Use @version for updates
- Document dependencies - List in @description or comments
- Test in incognito - Catch extension conflicts
- Respect site performance - Don't block rendering
- Follow site ToS - Don't abuse automation
Templates Available
- Minimal starter templatetemplates/basic-template.user.js
- Full-featured with utilitiestemplates/advanced-template.user.js
- Cross-origin API requeststemplates/api-integration.user.js
- DOM manipulation & stylingtemplates/ui-enhancement.user.js
- Single Page Application supporttemplates/spa-compatible.user.js
Scripts Available
- Generate new userscript from templatescripts/generate-userscript.sh
- Validate script for common issuesscripts/validate-userscript.sh
- Extract and validate metadata blockscripts/extract-metadata.sh
- Bundle external libraries into scriptscripts/bundle-library.sh
Reference
- Tampermonkey Documentation: https://www.tampermonkey.net/documentation.php
- GreasyFork Best Practices: https://greasyfork.org/help/code-rules
- GM API Reference: https://wiki.greasespot.net/Greasemonkey_Manual:API
- UserScript Examples:
directoryexamples/