Use when writing Tampermonkey/Violentmonkey/Greasemonkey userscripts, modifying website behavior or appearance, or working with .user.js files.
Scanned 9/5/2026
Install to Claude Code
npx -y skills add denolfe/dotfiles --skill userscripts --agent claude-codeInstalls into .claude/skills of the current project.
Are you the author of Userscripts?
Add the live security badge to your README — it updates automatically with every re-scan.
[](https://www.skillsdirectory.com/skills/denolfe-userscripts)More formats (shields.io, HTML) on the badges page.
---
name: userscripts
description: Use when writing Tampermonkey/Violentmonkey/Greasemonkey userscripts, modifying website behavior or appearance, or working with .user.js files.
---
# Userscript Development
Guide for writing Tampermonkey/Violentmonkey userscripts.
## Workflow
### Phase 1: Requirements
Ask one at a time:
1. **Target site** - URL pattern (e.g., `https://example.com/*`)
2. **Goal** - CSS styling, add feature, remove element, keyboard shortcut, etc.
3. **Output location** - Where to save the `.user.js` file
### Phase 2: DOM Inspection
**Option A: Fetch URL directly (try first for public sites)**
- Use WebFetch to retrieve page HTML
- Search for relevant selectors, CSS variables, data attributes
- Works for public sites not behind login
**Option B: User provides MHTML/HTML file**
- If fetch fails (login required, dynamic content), instruct: "Save the page (Cmd+S / Ctrl+S) as MHTML or 'Webpage, Complete'"
- Grep for patterns: `class=`, `data-`, `id=`, `--` (CSS variables)
- Prefer stable selectors: `data-testid`, `data-*`, semantic IDs over generated class names
**Option C: User provides selectors**
- Guide user to inspect element in browser devtools
- Ask for: element selector, parent structure, relevant CSS variables
### Phase 3: Implementation
1. Choose `@run-at` timing based on task type
2. Choose `@grant` level (start with `none`)
3. Write script using metadata template below
4. Save to user-specified location
### Phase 4: Testing
- Instruct: "Install in Tampermonkey/Violentmonkey and reload the page"
- If issues: iterate on selectors or timing
---
## Metadata Template
```javascript
// ==UserScript==
// @name Script Name
// @namespace your-namespace
// @version 0.0.1
// @description Brief description
// @icon https://www.google.com/s2/favicons?sz=64&domain=example.com
// @homepage https://github.com/username/repo
// @match https://example.com/*
// @exclude https://example.com/admin/*
// @author Author Name
// @run-at document-end
// @noframes
// @grant none
// ==/UserScript==
```
---
## Full Templates
### CSS Override (copy-paste ready)
```javascript
// ==UserScript==
// @name Site Name - Custom Styles
// @namespace your-namespace
// @version 0.0.1
// @description Override styles on example.com
// @icon https://www.google.com/s2/favicons?sz=64&domain=example.com
// @match https://example.com/*
// @author Your Name
// @run-at document-start
// @grant none
// ==/UserScript==
;(() => {
'use strict'
const style = document.createElement('style')
style.textContent = `
/* Hide element */
.annoying-banner {
display: none !important;
}
/* Override CSS variable */
:root {
--site-font: -apple-system, BlinkMacSystemFont, system-ui, sans-serif !important;
}
`
document.documentElement.appendChild(style)
})()
```
### DOM Manipulation (copy-paste ready)
```javascript
// ==UserScript==
// @name Site Name - DOM Tweaks
// @namespace your-namespace
// @version 0.0.1
// @description Modify DOM on example.com
// @icon https://www.google.com/s2/favicons?sz=64&domain=example.com
// @match https://example.com/*
// @author Your Name
// @run-at document-end
// @noframes
// @grant none
// ==/UserScript==
;(() => {
'use strict'
// Remove elements
document.querySelectorAll('.unwanted').forEach(el => el.remove())
// Modify elements
document.querySelectorAll('a.external').forEach(link => {
link.setAttribute('target', '_blank')
})
// Add keyboard shortcut
document.addEventListener('keydown', e => {
if (e.target.matches('input, textarea, [contenteditable]')) return
if (e.key === 'j' && !e.ctrlKey && !e.metaKey && !e.altKey) {
e.preventDefault()
// action here
}
})
})()
```
### Dynamic Content (copy-paste ready)
```javascript
// ==UserScript==
// @name Site Name - Dynamic Handler
// @namespace your-namespace
// @version 0.0.1
// @description Handle dynamically loaded content on example.com
// @icon https://www.google.com/s2/favicons?sz=64&domain=example.com
// @match https://example.com/*
// @author Your Name
// @run-at document-end
// @noframes
// @grant none
// ==/UserScript==
;(() => {
'use strict'
const processElement = el => {
// modify element
}
// Process existing elements
document.querySelectorAll('.target').forEach(processElement)
// Watch for new elements
const observer = new MutationObserver(mutations => {
for (const mutation of mutations) {
for (const node of mutation.addedNodes) {
if (node.nodeType !== Node.ELEMENT_NODE) continue
if (node.matches('.target')) processElement(node)
node.querySelectorAll?.('.target').forEach(processElement)
}
}
})
observer.observe(document.body, { childList: true, subtree: true })
})()
```
---
## @run-at Timing
| Value | Use Case |
|-------|----------|
| `document-start` | CSS injection, intercept before render |
| `document-end` | DOM manipulation (DOM ready, images loading) |
| `document-idle` | Non-critical, after page fully loads |
---
## @grant Options
| Value | When to Use |
|-------|-------------|
| `none` | Simple scripts, no sandbox, direct page access |
| `GM_addStyle` | CSS injection helper function |
| `GM_getValue` / `GM_setValue` | Persistent storage across sessions |
| `GM_xmlhttpRequest` | Cross-origin requests (also needs `@connect`) |
| `GM_registerMenuCommand` | Add items to extension popup menu |
| `GM_setClipboard` | Copy to clipboard |
| `unsafeWindow` | Access page's JavaScript context |
---
## Common Patterns
### CSS Injection
```javascript
// @run-at document-start
// @grant none
const style = document.createElement('style')
style.textContent = `
.selector {
display: none !important;
}
`
document.documentElement.appendChild(style)
```
### CSS Variable Override
```javascript
// @run-at document-start
const style = document.createElement('style')
style.textContent = `
:root {
--custom-property: value !important;
}
`
document.documentElement.appendChild(style)
```
### DOM Query + Modify
```javascript
// @run-at document-end
document.querySelectorAll('.selector').forEach(el => {
el.style.display = 'none'
})
```
### Keyboard Shortcut
```javascript
// @run-at document-end
document.addEventListener('keydown', e => {
// Skip if typing in input/textarea
if (e.target.matches('input, textarea, [contenteditable]')) return
if (e.key === 'j' && !e.ctrlKey && !e.metaKey && !e.altKey) {
e.preventDefault()
// action here
}
})
```
### MutationObserver (Dynamic Content)
```javascript
// @run-at document-end
const observer = new MutationObserver(mutations => {
for (const mutation of mutations) {
for (const node of mutation.addedNodes) {
if (node.matches?.('.target-selector')) {
// handle new element
}
}
}
})
observer.observe(document.body, { childList: true, subtree: true })
```
### GM_addStyle (with grant)
```javascript
// @grant GM_addStyle
GM_addStyle(`
.selector {
display: none !important;
}
`)
```
---
## Advanced Patterns
See `@references/advanced-patterns.md` for full implementations:
| Pattern | Use Case |
|---------|----------|
| Wait for element | Poll with `requestAnimationFrame` until element exists |
| isTyping helper | Skip shortcuts when user is in input/textarea |
| Scroll with offset | Smooth scroll accounting for fixed headers |
| Dark mode | CSS media query, meta tag, or CSS variables |
| URL parsing | Extract domain/path with `new URL()` |
| GM_addElement | Programmatic element creation |
| SPA navigation | Handle YouTube events, History API |
| Debug logging | Toggleable console output |
| Focus management | Track selection with keyboard nav |
| TreeWalker | Iterate text nodes for search/highlight |
| CSS Highlight API | Highlight without DOM modification |
| Canvas overlay | Draw scroll position markers |
---
## Selector Strategy
**Prefer (stable):**
- `data-testid="value"` - test IDs rarely change
- `data-*` attributes - semantic, intentional
- Semantic IDs - `#main-content`, `#sidebar`
- Tag + role - `button[role="tab"]`
**Avoid (fragile):**
- Generated classes - `.css-1a2b3c`, `.sc-abc123`
- Deep nesting - `div > div > div > span`
- Index-based - `:nth-child(3)`
---
## Debugging Tips
1. **Test selectors in console first** - `document.querySelectorAll('.selector')`
2. **Check timing** - if elements missing, try `document-idle` or MutationObserver
3. **Inspect network** - for dynamic content, elements may load after initial render
4. **CSS specificity** - use `!important` to override site styles
5. **Console errors** - check devtools console for script errors
---
## Reference
- `@references/api-reference.md` - All metadata headers, @match patterns, GM_* functions
- `@references/advanced-patterns.md` - SPA handling, dark mode, scroll markers, TreeWalker, etc.
Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.
No comments yet. Be the first to comment!