RemixletDocs

How it works

Anatomy of a remixlet

The files in a remixlet folder, why it is a folder rather than one script file, what the manifest declares, the capabilities a remixlet can ask for, and how approval and versions work.

A remixlet is a small folder, closer to a tiny browser extension than to a single page script. This page is the reference for what is in it, and for why it has that shape. The API those files can call is on its own page, The rmx API.

The folder#

airbnb-hide-seen/
remixlet.json   where it runs and what it needs
main.js         page behavior
style.css       page presentation
rules.json      optional network rules
README.md       intent and repair notes
FileRole
remixlet.jsonThe manifest: match patterns for the sites it runs on, the files it uses, and the capabilities it requests with a written reason for each.
main.jsThe script. Runs in the remixlet's own isolated world on matching pages.
style.cssThe stylesheet, applied by the browser as CSS.
rules.jsonOptional declarative network rules, kept as data so the extension can schema-check them before it lends them any power.
README.mdWhat the feature is supposed to do, what the page was assumed to look like, and why it is built this way. A later agent reads intent here instead of guessing it from code.

The extension writes the whole set atomically. If the manifest names style.css and the agent forgets to supply it, the save fails before anything activates. That rule came from a real failure: a script once saved fine while its missing stylesheet made the feature look broken.

Why a folder and not one file#

A userscript is compact. One JavaScript file, a metadata block at the top, and special APIs supplied by its manager. We considered making that the native Remixlet format and decided against it. A remixlet separates the things the browser already treats separately, and one file would hide that structure without removing it. A single .user.js would still carry all five concerns. They would just be encoded as comment directives, JavaScript strings, and prose comments inside one larger file.

Chrome's userscript API does not read userscript files#

This is the practical reason. The metadata block of the one-file user-script format is a convention, not a specification, and each script manager implements its own dialect of it. None of those dialects is the input format of Chrome's userscript platform. How that convention relates to what Chrome reads is in Why change what works?.

Chrome's userScripts API accepts a registration object: an id, match patterns, JavaScript sources, a run time, and an execution world. It does not read a ==UserScript== block or implement @grant. Styles go through scripting.insertCSS() separately. Network blocking, redirects, and header changes use declarative network rules, which are data rather than page JavaScript.

So supporting a classic userscript file would not let Remixlet hand that file to Chrome. We would have to build a metadata parser, translate its fields into several extension APIs, unpack embedded CSS and data, and invent private directives for everything the format cannot express. remixlet.json is that registration data without the detour through comments.

Separate files are boundaries the extension can enforce#

For a short hand-written tweak, one file is a feature. For code an agent has to build, verify, revisit, and repair, it gets in the way.

  • CSS in style.css is parsed as CSS, applied by the browser as CSS, and reviewed as a stylesheet.
  • Network rules in rules.json stay declarative data. The extension schema-checks and constrains them before they get any network-level power.
  • README.md records what the user wanted, what the agent assumed, and why. A later agent recovers intent without reverse-engineering code.
  • Git can show that a fix changed only a selector in CSS or one rule in JSON, and rollback restores the complete working set.

The shape follows the extension#

Current Chrome extensions are projects with a JSON manifest and referenced files. Chrome's manifest documentation describes manifest.json as the record of an extension's structure and behavior, and its content-script format names JavaScript and CSS files separately. Static network rules likewise live in JSON files named by the manifest.

A remixlet is not itself an extension package, but its shape is deliberately close to one. Match patterns stay match patterns. Script timing and execution worlds stay explicit. Styles stay stylesheets. Network rules stay declarative rules. We build against the browser APIs that exist instead of treating userscript-manager conventions as an intermediate platform.

We tested the other direction with an earlier exporter. A userscript could carry the subset of a remixlet that mapped onto portable GM_* APIs, but not the whole runtime. That made it a useful export, never the source of truth.

What we kept from userscripts#

Almost all of the page-level model. Remixlet still registers the JavaScript through chrome.userScripts, uses standard match patterns, and runs it in a USER_SCRIPT world by default. Greasemonkey and Tampermonkey proved that small pieces of user-controlled code are a good way to reshape the web. Why the API is not GM_* either is covered in Why change what works?.

The manifest#

The manifest is strict JSON, validated the moment the agent writes it. A malformed manifest is a hard failure the agent has to repair before anything is stored.

remixlet.json
{
  "id": "bandcamp-album-lengths",
  "name": "Bandcamp album lengths",
  "description": "Show each album's total length in the grid.",
  "matches": ["https://*.bandcamp.com/*"],
  "scripts": [{ "file": "main.js", "runAt": "document_idle" }],
  "styles": ["style.css"],
  "capabilities": ["storage", "fetch:*.bandcamp.com"],
  "capabilityRationales": {
    "storage": "Remember which albums you've hidden.",
    "fetch:*.bandcamp.com": "Look up album lengths the page doesn't show."
  }
}
FieldMeaning
idLowercase letters, digits, and hyphens. Doubles as the folder name and the registration id.
name, descriptionShown in the extension. The name is capped at 60 characters.
matchesStandard extension match patterns. The remixlet's files run only on URLs these cover, and its network powers are pinned to them.
scriptsThe JavaScript files, in order. Each entry may set runAt (document_start, document_end, or document_idle) and world. The default world is USER_SCRIPT. Setting MAIN requires the page-world capability. Only .js and .mjs files are accepted.
stylesThe stylesheet files.
capabilitiesThe powers requested. Every entry needs a matching rationale.
capabilityRationalesOne plain-language reason per capability, 1 to 500 characters. This is the text you see on the approval screen.
netRulesThe name of a JSON file of network rules. Requires the netrules capability.
versionStamped by the store on activation.
builtWithStamped by the extension at write time: the rmx.* contract version and the extension release that wrote the code.

The last two fields belong to the extension, not the model. Whatever the model supplies for them is overwritten.

A userscript header mainly tells a manager where and when to run a file. A remixlet manifest has a second job. It is the typed handoff from the code-writing agent to the enforcing extension. The agent may request capabilities, but the manifest cannot grant them. The extension asks you separately, records approval against the exact artifact, and checks the grant outside the script on every rmx.* call. Strict JSON does not make code trustworthy by itself. It makes the boundary machine-checkable and lets the two sides own different parts of the record.

We could have encoded this as more @grant-style directives. But network rules, background schedules, capability rationales, and compatibility stamps would all be Remixlet-only, while classic directives such as @require and update URLs describe a remote-code model Remixlet deliberately does not use. A different format is more honest than a familiar header followed by directives we ignore and private ones no other manager understands.

Capabilities#

A fresh remixlet world contains standard JavaScript, the page's HTML, and nothing else. Every power beyond that is a named capability, declared in the manifest with a reason, and approved by you before the remixlet activates.

CapabilityWhat it allows
storageA private key-value store for the remixlet.
clipboardWriting text to the clipboard. Write only.
notificationsShowing text notifications. Text only.
menuAdding commands to the remixlet's menu in the toolbar popup.
scheduleRunning at a later time through the extension's alarms.
fetch:api.example.comRequests to that one host, and nothing else. fetch:*.example.com covers the apex and every subdomain.
network:observe:api.example.comReading the API responses the page itself fetches from that host. The interceptor is the extension's code, scoped to the one host, capped in how much it keeps, and a pure pass-through.
netrulesBlocking, redirecting, or reshaping requests through declarative rules. Rules are pinned to the remixlet's own sites, may not touch security headers, and may only redirect to http(s) targets.
page-worldRunning in the page's own JavaScript world instead of the sandbox. A named grant whose whole job is to make "this code runs unsandboxed" something you explicitly say yes to.

There is no "access the internet" grant, on purpose. Network powers are always scoped to named hosts. What each capability unlocks in code is on The rmx API.

Approval#

Approval is bound to a SHA-256 digest of the exact files being proposed, so a yes to one proposal cannot authorize swapped-in code. The dialog shows the sites, the capabilities in plain words, and the remixlet's stated reasons; the digest ties that yes to the code. There is no separate grant record. Activation is the only way files reach the store, and it refuses any set whose capabilities you have not approved, so the stored manifest is the record of what you allowed. Delete the remixlet and its permissions go with it.

Updates re-open the approval screen whenever a new or broader capability appears, and "broader" includes the site list. A manifest that asks to run on every site, or on more sites than the version you approved, opens the dialog even with zero capabilities. Network rules are compared file against file with the version you approved, so editing the rules re-prompts too.

Declaring a capability does not hand the remixlet any machinery. Each rmx.* call travels to the extension's background worker, which checks the caller's identity and the granted list before doing anything. Security architecture covers this in full.

Versions and history#

Every remixlet keeps its own git history. Each change is a commit you can open as a colored diff, and rolling back to any version is one click. Rollback restores the complete working set, not whichever fragments happened to be edited.

Every remixlet is also stamped with the rmx.* contract version it was written against. Within a version the API only grows: no renames, no removals, no behavior changes to anything stored code can call. A remixlet outside the supported range is parked as needing attention instead of being injected to break.

What a remixlet cannot do#

  • Load a script from the network. A remixlet runs only the bytes stored in your browser. Each world carries a Content-Security-Policy that blocks scripts from the network, dynamic import(), eval and Function, and the extension parses every file save and rejects a remote import(), a created script tag, an eval of anything but a literal, and an element src pointed at a remote URL. One browser gap remains: a string handed to a timer runs as code inside the world, and only the save-time check stands in its way.
  • Update itself. The extension never fetches a remixlet's code from anywhere; the only code that changes is code you approved or an agent wrote in a chat with you.
  • Arrive from someone else. Nobody can publish a remixlet into your browser, and there are no install links or marketplace today.
  • Leave the extension. Remixlets are not converted into standalone scripts. An exported script would carry its powers outside these walls with no one checking calls at the door.