Back to skills
SKILL.md
Userscripts
ASecurityUse when writing Tampermonkey/Violentmonkey/Greasemonkey userscripts, modifying website behavior or appearance, or working with .user.js files.
- 87 stars
- 0 votes
- 0 copies
- 2 views
- Added September 5, 2026
Works with
Security analysis
100/100Pro scans all 3 files and shows the line behind each finding
npx -y skills add denolfe/dotfiles --skill userscripts --agent claude-codeAre you the author of Userscripts?
Add the live security badge to your README. It updates with every re-scan.
[](https://www.skillsdirectory.com/skills/denolfe-userscripts)---
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)**
- Fetch the 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'"
- Search the HTML 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.
Files in this skill
- SKILL.md
- references/advanced-patterns.md
- references/api-reference.md
Attribution
Comments
Loading comments…
Newsletter
Get the weekly list of safe new skills, and the ones we pulled.