RemixletDocs

How it works

Anatomy of a remixlet

The files in a remixlet, a complete example, what the manifest records, the permissions a remixlet can hold, and how versions work.

A remixlet is a small folder of files, closer to a tiny browser extension than a user script. This page covers what is in the folder and why it has that shape. The APIs its script calls are in the dom.* and rmx.* references.

The folder#

hide-listings/
remixlet.json   where it runs and what it may use
main.js         what it does on the page
style.css       how it looks
rules.json      optional network rules
README.md       what you asked for, and how it works
FileRole
remixlet.jsonThe manifest: the sites it runs on, its files, and the permissions it holds, with a reason for each. The extension builds this file; the agent never writes it directly.
main.jsThe script. It runs in the remixlet's own sandbox on matching pages and reaches the page only through the dom API.
style.cssThe stylesheet. The extension inserts it into matching pages after checking what it loads: only full https or http addresses on the remixlet's own sites or its fetch: hosts.
rules.jsonOptional network rules that block, redirect, or change the page's requests. They are data, checked against fixed limits before they are installed.
README.mdWhat you asked for, what the agent assumed about the page, and why it built the remixlet this way. A later fix reads your intent here instead of guessing it from the code.

The agent writes the script, stylesheet, rules, and README, and declares the name, sites, permissions, and reasons. The extension builds the manifest from those declarations and saves the whole set as one version. You can read every file of every version in the manager, under History › "Code".

A complete example#

This remixlet adds a "Hide" button to each listing on a site, and keeps the listings you hid out of sight on later visits.

remixlet.json
{
  "id": "hide-listings",
  "name": "Hide listings",
  "matches": ["https://listings.example.com/*"],
  "scripts": [{ "file": "main.js", "runAt": "document_idle" }],
  "styles": ["style.css"],
  "capabilities": ["storage"],
  "capabilityRationales": {
    "storage": "Remember which listings you hid."
  },
  "builtWith": { "bridge": 2, "extension": "1.0.1" },
  "version": 1
}
main.js
async function hiddenIds() {
  return new Set((await rmx.storage.get("hidden")) ?? []);
}

// Every listing gets a mark once it is handled, so this keep only works on
// listings the site added since the last pass.
rmx.keep("Hide button on each listing", {
  when: () => dom.query(".listing"),
  ensure: async () => (await dom.queryAll(".listing:not([data-rmx--done])", { limit: 1 })).length === 0,
  apply: async () => {
    const hidden = await hiddenIds();
    for (const listing of await dom.queryAll(".listing:not([data-rmx--done])")) {
      await listing.setAttr("data-rmx--done", "");
      const id = await listing.data("id");
      if (hidden.has(id)) {
        await listing.addClass("rmx--hidden");
        continue;
      }
      const button = await dom.create("button", { text: "Hide", classes: ["rmx--hide"] });
      await listing.append(button);
      button.on("click", async () => {
        const ids = await hiddenIds();
        ids.add(id);
        await rmx.storage.set("hidden", [...ids]);
        await listing.addClass("rmx--hidden");
      });
    }
  },
});
style.css
.rmx--hidden {
  display: none !important;
}

.rmx--hide {
  margin-left: 8px;
}
README.md
# Hide listings

Adds a Hide button to every listing on listings.example.com. A hidden
listing stays hidden on later visits, including listings that load as
you scroll.

Assumes each listing is a `.listing` element with a `data-id` attribute.
If buttons stop appearing, check that selector first.

A few things to notice:

  • The script never touches the page directly. Every dom call is a message to the page agent, which checks it and answers. That is why almost every line has an await.
  • rmx.keep does the repeating. The site adds listings as you scroll; the keep runs again after each batch of page changes, and ensure tells it when there is nothing left to do.
  • Marks carry the remixlet's id. A class or attribute a remixlet adds to one of the site's own elements must be a mark, written rmx--<name>. The extension fills in the remixlet's id, so rmx--hidden in main.js and in style.css is the same class on the page. See ownership marks.
  • Storage is a permission. rmx.storage works only because the manifest holds storage and you allowed it.

Why a folder and not one file#

A user script is one JavaScript file with a metadata block at the top. We considered making that the native format and decided against it. A remixlet has several parts that the browser already treats as separate things: page code, stylesheets, network rules, and permissions. One file would hold the same parts, encoded as comment directives, strings, and comments inside a bigger script.

Separate files let the extension handle each part as what it is:

  • The manifest is strict JSON that the extension builds and validates. It is the handoff between the agent, which may ask for permissions, and the extension, which asks you and enforces them. The manifest cannot grant anything by itself.
  • Network rules stay declarative data, checked against fixed limits before they get any power.
  • A stylesheet is read as CSS, checked for what it loads, and applied by the browser as a stylesheet.
  • The README records what you wanted and what the agent assumed, so a later agent recovers your intent without reverse-engineering code.
  • The version history can show that a fix changed one selector in the CSS or one rule in the JSON, and restoring a version brings back the complete working set.

The shape follows Chrome's own extensions: a JSON manifest that names its files, match patterns for where it runs, stylesheets as stylesheets, and network rules as data. A remixlet is not an extension package, but it uses the ideas the browser already has, not a user-script manager's conventions.

We tested the other direction with an early exporter. A user script could carry the part of a remixlet that mapped onto the portable GM_* functions, but not the rest, so it was only ever an export, never the source. Why the API is not GM_* either, and why the code does not run in the page at all, is covered in Security architecture.

The manifest#

The extension builds remixlet.json from what the agent declares, and checks it before anything is stored. A manifest that fails a check stops the save, and the agent has to fix its declaration.

FieldMeaning
idAssigned by the extension from the name: lowercase letters, digits, and hyphens, up to 40 characters, with a number added if the id is taken. It is also the folder name, and the id in the remixlet's marks.
nameShown in the popup, the manager, and the chat. At most 60 characters, with no control or text-direction characters.
matchesStandard extension match patterns. The remixlet runs only on addresses these cover, and its network permissions are tied to them. A wildcard may not span a public suffix such as *.com. A pattern that covers every site is allowed, but you are asked first.
scriptsThe JavaScript files, main.js first. Only .js and .mjs files are accepted. runAt sets when the scripts start: document_start as soon as the sandbox is ready, document_end once the page's HTML has loaded, or document_idle once the page has finished loading.
stylesThe stylesheets, style.css first.
capabilitiesThe permissions the remixlet holds. Each needs a reason.
capabilityRationalesOne plain-language reason per permission, 1 to 500 characters. It is kept as a record for you and for later agents. The approval screens use fixed text instead, so a manipulated model cannot write what you decide on.
netRulesThe name of the network rules file. Requires the netrules permission.
versionStamped by the extension when the version is saved.
builtWithStamped by the extension: the API version the code was written against (bridge) and the extension release that wrote it.

Permissions#

A fresh remixlet has standard JavaScript, the dom API for the page it runs on, and nothing else. Anything more is a permission, called a capability in the manifest. The agent asks for it with a reason, and you approve it in the chat before the code is written.

CapabilityWhat you see in the chatWhat it allows
storageRemember things between visitsA private key-value store for this remixlet.
clipboardCopy text to your clipboardWriting text to the clipboard. It cannot read it.
notificationsSend you notificationsShowing text notifications.
menuAdd an item to the Remixlet menuCommands in the toolbar popup that run the remixlet's code on the current page.
scheduleRun on a schedule and open tabsTimers that show a notification, open one of the remixlet's own pages, or leave a message for its next page load. No remixlet code runs on the timer itself.
fetch:api.example.comSend requests to api.example.comRequests to that host, signed in as you, and nowhere else. It also lets the stylesheet load images and fonts from that host. fetch:*.example.com covers the domain and all its subdomains.
network:observe:api.example.comSee what this page loads from api.example.comReading the responses the page itself receives from that host.
netrulesChange what pages loadBlocking, redirecting, or changing the requests pages make, through the rules file: on the remixlet's own sites, or on every site for one that runs everywhere. Never Remixlet's own requests. Security headers are off limits. A redirect applies only to GET requests and goes to an https address on the remixlet's own sites, or on the same site as the hosts the rule names.

There is no "access the internet" permission. Network permissions always name their hosts. The rmx.* API reference lists what each one unlocks.

Approval#

You approve a remixlet's permissions in the chat, and again in a dialog if a new version would run on more sites, on sites other than the chat's, or with changed network rules, or if a chat would move a remixlet that also runs elsewhere onto its own site. The extension ties each activation to the exact files, and the background worker checks the permission again on every privileged call. How approval works has the details.

Versions and history#

Every save is a new version in the remixlet's own history, kept in your browser. The manager shows each version's code and what it changed, grouped by the chat that made it, and "Restore" brings back the complete set of files from any version. If an older version held permissions the current one does not, restoring it asks you to allow them again.

Every remixlet is also stamped with the version of the dom and rmx API it was written against. Within a version the API only grows: no renames, no removals, and no changes to anything stored code can call. If a remixlet's stamp falls outside what the installed extension supports, the extension does not run it and marks it "Not running", instead of letting it misbehave.

What a remixlet cannot do#

  • Load code from the network. A remixlet runs only the code stored in your browser. The sandbox blocks scripts and import() from the network, eval, Function, and string timers, and the extension rejects obvious remote-code patterns when the code is saved.
  • Update itself. The extension never fetches a remixlet's code from anywhere. Code changes only when an agent writes it in a chat with you, or when you restore a version.
  • Read the site's secrets. It cannot read the site's cookies, storage, or JavaScript variables, or the values of password fields.
  • Run with no page open. Its code runs only while a page it matches is open. Between visits, only a schedule's fixed actions run.
  • Arrive from someone else. Nobody can put a remixlet into your browser. There are no install links and no marketplace.
  • Leave the extension. Remixlets cannot be exported as standalone scripts. An exported script would take its permissions somewhere nothing checks its calls.