Userscript
Purpose
Provide portable defaults for userscripts that are resilient to DOM changes, scoped safely, and explicit about permissions and page integration.
When to use this skill
- Writing or reviewing a userscript.
- Designing the metadata block, grants, and match patterns.
- Injecting UI into third-party pages.
- Automating DOM interactions or page data extraction.
Scope Boundaries
- Use this skill when the code runs as a userscript on third-party pages and the metadata block, grants, or page integration rules dominate the design.
- Use
ref-sp-js-web-standalone-templatewhen the browser code lives in an app you control rather than in a userscript manager. - Use
ref-sp-js-javascriptfor plain JavaScript structure and JSDoc concerns that are not userscript-specific.
Defaults
- Keep the metadata block explicit and minimal.
- Prefer
.user.jsfor plain JavaScript userscripts and.user.tsonly when the repo has a deliberate TypeScript or build flow for userscripts. - Use the narrowest
@matchor@includepatterns that still fit the task. - Declare grants deliberately; do not request APIs you do not use.
- Keep startup idempotent so re-runs do not duplicate UI or listeners.
- Isolate selectors, storage keys, and injected class names as constants.
- Keep fixed mode names, labels, and state tags as const literals and derive any needed unions from those values instead of repeating them in a separate type declaration.
- Prefer
constarrow functions for userscript helpers. If a wrapper is useful, prefer an arrow IIFE such as(() => { ... })();unless an existing file already uses a different wrapper consistently.
Core Rules
Metadata and permissions
- Choose precise page scopes rather than broad wildcards where possible.
- Add
@grantentries only for the userscript APIs the code actually uses. - Keep version, name, and description clear enough that the installed script is recognizable.
DOM behavior
- Wait for the necessary page state before acting.
- If the target page is highly dynamic, prefer a small targeted observer over polling loops.
- Namespace injected CSS classes, IDs, or data attributes to avoid clashing with the page.
State and safety
- Make UI injection and event binding idempotent.
- Keep selectors and mutation rules centralized so page changes are easier to repair.
- Keep helper functions in the same const-arrow style as ordinary JavaScript and TypeScript modules unless a userscript-manager compatibility constraint forces a different shape.
- In JSDoc-checked userscripts, keep closed lookup objects on
/** @type {const} */and derivekeyof typeofor value unions from the literal instead of widening them to generic records. - In JSDoc-checked userscripts, import external types with
/** @import { SomeType } from './somewhere.js' */instead of duplicating upstream typedefs locally. - Let helper return types be inferred unless the userscript exposes a public API, callback contract, or non-obvious union that needs an explicit annotation.
- Fail softly when the page no longer matches expectations.
File and type support
- Use
.user.jsor.user.tsin the filename so the userscript role is obvious in code search and tooling. - If TypeScript or strict linting checks the userscript, keep a small
userscript-globals.d.tsor similar ambient declaration file beside it rather than scattering missing-global suppressions. - Keep userscript-specific config separate from ordinary browser modules so grants and globals do not leak into unrelated code.
Task Framing
| Command or action | What | Why | When | Expected outcome |
|---|---|---|---|---|
| Define metadata scope | Choose @match, @grant, and startup metadata deliberately. |
Over-broad scope and unused permissions make userscripts harder to trust and maintain. | Before writing the implementation. | The userscript runs only where intended and requests only the APIs it uses. |
| Inject UI safely | Add UI, listeners, and styles in an idempotent way. | Dynamic pages and repeated initialization are common failure modes. | When augmenting an existing page. | Re-runs do not duplicate UI or handlers. |
| Centralize selectors and storage | Keep selectors, storage keys, and injected identifiers in named constants. | Host pages change often, and scattered selectors make repairs expensive. | Whenever the script reads or mutates page state. | The DOM integration surface is easy to update and review. |
Gotchas
- Broad
@matchpatterns and unused grants create unnecessary risk. - Polling loops are usually a smell; prefer targeted observers or state checks.
- Injected IDs and class names need namespacing or they will collide with host-page styles and scripts.
Validation
- The metadata block requests only the permissions and pages actually needed.
- Re-running the script does not duplicate injected UI or listeners.
- Page selectors and injected identifiers are centralized and namespaced.
- Userscript helpers follow the const-arrow default unless a compatibility constraint justifies a function declaration.
- The script degrades cleanly when the host page changes.
References
- Tampermonkey Documentation: https://www.tampermonkey.net/documentation.php
- Violentmonkey Metadata Block: https://violentmonkey.github.io/api/metadata-block/
- Read
./references/checklist.mdfor a quick userscript review pass. - Read
./references/types-and-config.mdwhen you need naming, ambient global, or config examples for.user.jsand.user.tsworkflows. - Read
./assets/trigger-eval-queries.example.jsonwhen testing whether the description activates on userscript and DOM-automation prompts. - Review
./evals/evals.jsonwhen checking output quality for high-risk page automation work.