How it works
Why change what works?
Userscripts have worked for twenty years. Remixlet does the same job with its own file layout and its own API. One of those was necessary and one was a preference, and this page is honest about which.
A userscript is one JavaScript file with a comment block at the top and a handful of GM_* functions supplied by the manager. It has worked since 2005. Remixlet does the same job, small scripts that reshape websites, and yet stores its scripts as a folder with a JSON manifest and gives them an API called rmx. "We invented our own" is usually a mistake, so we owe an explanation.
Here is the short version. A single file with a Remixlet-owned header would have worked. We did not need to invent a folder, and we say below what the folder buys and why we took it anyway. What we did need to invent is the API, because the thing a remixlet is allowed to do, and who decides, is different from anything GM_* was built for.
What the browser reads#
Start with what Chrome takes, because the one-file format is not it.
Under Manifest V3, an extension registers user code through the userScripts API. The input is a registration object. An id, a list of match patterns, the JavaScript sources, when to run, and which world to run in. That is all. Chrome does not read a ==UserScript== block. It does not implement @grant, @require, or @connect. It does not supply GM_setValue. Every one of those is something a script manager, itself an extension, builds on top of the same registration call we use.
Everything else a remixlet does goes through a different Chrome API, each with its own input shape:
- Styles go through
scripting.insertCSS(), which takes CSS text. TheuserScriptsregistration has no field for CSS. - Blocking and redirecting requests goes through
declarativeNetRequest, which takes rule objects, not code. Chrome applies them itself; no script runs. - Running something later, with no tab open, goes through
alarms, which only the extension's background worker can use. - Each
USER_SCRIPTworld gets its own Content-Security-Policy. Ours isscript-src 'self', so a remixlet cannot load a script from a server even if it tries.
Two more MV3 facts shape the rest of this page. Extension logic has to be self-contained; the User Scripts API is the documented exception that lets an extension run code it did not ship, and Chrome makes the user switch it on. And the background is a service worker that Chrome stops whenever it likes, so nothing that decides what a remixlet may do can live in memory. It has to be re-read from storage on every call.
So the one-file format sits one layer above the browser. It is a convention among managers, and the managers do not agree on it. Each documents its own directives, its own URL matching, and its own GM_* set, and each has changed its own meaning over time. Greasemonkey changed what a missing @grant line meant twice and then renamed its entire API in version 4. Tampermonkey and Violentmonkey kept the old names and added their own. A script is portable inside the overlap of whichever manager its author tested with. That is fine for a human who notices the breakage and patches it. Copying the format would have meant picking one dialect, and no dialect is what Chrome takes.
The API had to be ours#
This is the decision that mattered.
Page-script APIs stop at the page#
GM_* covers what a script inside an open page wants. Store a value, make a request, add a menu command, copy text, show a notification. A remixlet also needs the extension around it. It blocks or rewrites requests before they leave the browser, watches the responses a page fetches for itself, runs at a set time with no tab open, and pauses when you tell it to, even in a tab that already loaded it. Those live in extension APIs no page script can reach, and no GM_* function was ever named for them.
We could have kept the GM_* names for the overlap and invented a second family for the rest. That is two overlapping contracts to keep stable and scripts that only look portable. One contract was the smaller system. Every call a remixlet can make is on The rmx API.
@grant is a label; a capability is a check#
The one-file format's answer to power is @grant. The script names the privileged functions it wants and installing it accepts the package. Enforcement happens once, at install, or in Tampermonkey's case with a per-host confirmation the first time a request goes out. The unit of trust is the whole script, and that works because a human chose it and could read it.
Our author is a model that reads a live page, and the page may contain text written to steer it. So the manifest can request a capability but cannot grant one. Every capability is named and scoped, fetch:api.example.com rather than "make requests", and every one carries a written reason that you see before approving. Approval is bound to a digest of the exact files being proposed; the dialog shows the sites and the capabilities in plain words, and the digest ties your yes to the code. After that, activation is the only way anything reaches the store, and it refuses a file set whose capabilities you have not approved, so the stored manifest is the approval record. The background worker reads that manifest on every rmx call, from storage, because MV3 gives it nowhere else to keep the answer. Pause a remixlet and the next call fails, even from code already running.
We could have hidden those rules behind functions named GM_xmlhttpRequest. They would keep the name and break the promise the name makes. The full enforcement story is in Security architecture.
Stored code cannot be regenerated#
This is the quietest reason and probably the strongest. The conversation that wrote a remixlet is over. There is no build step, and nobody is expected to patch every stored script by hand. If the API underneath ever changed shape, the remixlet would fail at runtime, on your page, with nothing in between to catch it.
So rmx.* is a published contract. Within a version it only grows. No renames, no removals, no behavior changes to anything stored code can call. The extension stamps the contract version into every remixlet when it writes it, and a remixlet outside the supported range is parked as needing attention instead of injected to break.
GM_* never had that. When Greasemonkey 4 renamed its API, every stored script that called GM_setValue stopped working there and kept working in Tampermonkey. Nothing parked them; they failed on the page. And copying GM_* would have meant choosing which manager's behavior to freeze, then carrying that compatibility layer next to our real one. Owning a smaller API gives stored code one definition of correct.
The folder is a preference#
Now the honest part. All of the above could sit in one file with our own header. The data is the same. We chose a folder for reasons we like, not reasons we needed.
The browser takes each part of a remixlet through a different API, and separate files let us validate each part in its own language. The stylesheet is checked as CSS. Network rules stay JSON, which the extension can restrict field by field before they get any network-level power. A remixlet can also mix a sandboxed script with a MAIN-world one, and two worlds are two entry points. A README.md records what the feature was for and what the page was assumed to look like, so the agent that repairs it after a redesign reads intent instead of guessing it from code. Each remixlet keeps its own git history, and a fix that changed one selector shows as a diff of one line in one file.
Every one of those could be done inside a single file with strings, comments, and a sidecar. It would be uglier and harder to check, and we did not want to spend our effort there. Weigh that as taste. The full breakdown of what is in the folder, and how it maps onto Chrome's APIs, is on Anatomy of a remixlet.
A familiar name is a promise#
If remixlets opened with ==UserScript== and called GM_setValue, anyone who has used a script manager would expect their existing scripts to install. That is what a shared format is for. But inbound scripts are third-party code written for a more generous permission model, and Remixlet has no install links and no marketplace today. We would look like part of that ecosystem while turning its scripts away. A visibly different format and a different namespace make the promise we can keep.
What we kept#
Everything below the file format. Remixlets register through chrome.userScripts, the API that exists because of the Greasemonkey lineage, run in a USER_SCRIPT world, and use the same match-pattern syntax for where they run. The idea that your browser should bend websites to you and not the other way around is twenty years old, and we are grateful for it. The format and the API are where that idea had to change to survive a new kind of author.