Getting Started With Userscripts: A Practical Introduction

A userscript is a small piece of JavaScript your browser injects into pages you choose. It is the fastest way to fix an annoying website, automate a repetitive click, or add the feature a product team never shipped. This guide takes you from installing a manager to writing, debugging and maintaining your own scripts.
What a userscript actually is
A userscript is a plain JavaScript file with a comment block at the top describing where it should run. A manager extension reads that block, matches it against the page you are visiting, and injects the code at the moment you asked for.
That is the whole model. There is no build step, no packaging, no store review and no publishing process. You save a file, the manager runs it, and the page changes.
Compared with a browser extension, a userscript trades power for speed of iteration. It cannot add toolbar UI or intercept network traffic at the browser level, but you can write, test and fix one in the time it takes to scaffold an extension manifest.
Choosing a manager
Userscripts run inside a manager extension. Tampermonkey is the most widely supported and has the largest API surface. Violentmonkey is the open-source alternative and behaves nearly identically for everyday scripts. On iOS and macOS Safari, Userscripts is the common choice, with a smaller feature set.
Once a manager is installed, opening any URL ending in .user.js triggers an install prompt instead of a download. That is the entire installation flow, and it is why install links look like ordinary file links.
Pick one manager and stay with it. Running two at once means both will try to claim .user.js URLs and you will end up with duplicate scripts firing on the same page, which is confusing to debug.
Reading the metadata block
Every userscript begins with a comment block that tells the manager where and how to run. Read it before you install anything: it is the script permission list, and it is short enough to skim in ten seconds.
- @match and @include: which URLs the script runs on. Be wary of patterns matching every site.
- @grant: which privileged APIs it can call, such as storage, clipboard or cross-origin requests.
- @connect: which external hosts it may talk to.
- @run-at: when it executes relative to page load.
- @require: external libraries pulled in at runtime, which are code you are also trusting.
- @version and @updateURL: how the manager decides an update is available.
Understanding @match and @run-at
Most first-time breakage comes from these two directives. A pattern like https://example.com/* matches paths on that host only, while https://*.example.com/* also covers subdomains. Getting this wrong is why a script silently does nothing.
The timing directive matters just as much. document-start runs before the page parses, which is right for blocking something before it appears. document-idle, the default, runs after the DOM is ready and is right for almost everything else.
When a script targets a single-page application, neither setting is enough on its own. The manager runs your code once, but the app keeps replacing content without a navigation, so you need to observe changes rather than assume the page is final.
Installing safely
Userscripts have full access to the pages they match, including anything you are logged into. A script matching your email or your bank can read everything on screen. Treat them like browser extensions, not like snippets.
The good news is that userscripts are usually short and always readable. Unlike a compiled extension, there is nothing hidden: the code you install is the code that runs.
- Read the source before installing, good scripts are short enough to skim.
- Prefer scripts whose source lives somewhere public with visible history.
- Reject broad match patterns unless the script genuinely needs every site.
- Be suspicious of long obfuscated strings, remote eval and unexplained @connect hosts.
- Review updates, because auto-update means tomorrow code is not today code.
- Disable rather than delete when debugging, so you can bisect which script broke a page.
Your first script
Start with something trivially verifiable: hide an element, stop a video autoplaying, or log a value to the console. Confirming that your code runs at all is the hardest step, and everything after it is ordinary DOM work.
Wrap your logic so it runs once the elements exist. On a static page, that means waiting for DOMContentLoaded or using the default idle timing. On a client-rendered app, it means watching for the element to appear.
Keep the first version ugly. Query the element, change it, reload the page. Once it works, tidy up: name your selectors, guard against nulls, and add a short comment explaining why the fix exists, because in six months you will not remember.
Handling pages that render late
Most modern sites build their content in JavaScript after the initial HTML arrives. If your selector returns null, the element almost certainly had not rendered yet.
The reliable pattern is a MutationObserver watching a stable container, doing your work when the target appears, and disconnecting once it is done. Polling with a timer works too, but it keeps burning cycles long after the page has settled.
Also plan for the element disappearing again. Infinite scroll, tab switches and client-side navigation all destroy and rebuild nodes, so a script that runs exactly once will look broken the second time the user reaches the same view.
Storage, styles and privileged APIs
A script that needs to remember a preference should use the manager storage API rather than page localStorage, because page storage is shared with the site and can be cleared by it.
For visual changes, injecting a stylesheet is nearly always better than setting inline styles element by element. One rule survives rerenders, while inline styles vanish when the framework replaces the node.
Cross-origin requests need an explicit grant and an allowed host. If you find yourself wanting to fetch a third-party API from a userscript, weigh whether the feature is worth granting your script network reach it did not previously have.
Debugging tips
Open DevTools and check the console for your script name. Managers namespace errors, so you can tell yours apart from the site own noise. If nothing appears at all, the match pattern is almost always the culprit.
Verify in stages: log a line at the top of the script, then log the result of your selector, then log inside your handler. Whichever log stops appearing tells you exactly which assumption failed.
Site updates are the second most common cause of breakage. A class name changes, your selector stops matching, and the script silently does nothing. Prefer stable hooks such as ARIA roles, data attributes and visible text over generated class names.
- No output at all: the script is not running, so check @match.
- Output but no effect: the selector matched nothing, so check timing.
- Works once then stops: the node was replaced, so add an observer.
- Works logged out but not logged in: the layout differs, so handle both.
Keeping scripts maintainable
Bump the version in the metadata block whenever you change behaviour. Managers use it to decide whether an update exists, and a version history gives you something to roll back to.
Write a one-line changelog per release. It costs nothing and turns a folder of anonymous scripts into something you can audit months later when a site changes and half your fixes stop working.
Export your script list periodically. Managers can sync, but a plain export you control is the difference between a five-minute restore and rebuilding a personal toolkit from memory.
When a userscript is the wrong tool
If you need to change requests before they leave the browser, add persistent interface chrome, or run without a visible tab, you want a real extension. Userscripts live inside a page and cannot reach past it.
If a site offers an API or an export, use it. A script that scrapes a page is one redesign away from breaking, while a documented endpoint tends to keep working.
And if the change is only about appearance, a user stylesheet is lighter and safer than shipping JavaScript into every page you visit.
A realistic first week
A good starting order is: install a manager, install one script written by someone else, read its source, then modify a single line of it and watch the page change. That loop teaches more than any tutorial because the feedback is immediate and the stakes are zero.
From there, write one script of your own that fixes something you personally hit every day. Personal annoyance is the best specification available, and it keeps you motivated through the first round of selector debugging.
After that, the skill stops being about userscripts at all. It becomes ordinary DOM work with an unusual delivery mechanism, and everything you already know about JavaScript applies directly.
Questions about the tools in this guide
Short answers about the hubs this article touches, each linking straight to the tool.