Enabling Script Managers with Chromium's User Scripts API

This article explores how the Chromium User Scripts API enables script manager extensions to function under the strict security constraints of Manifest V3. It covers the architectural challenges posed by the ban on remotely hosted code, the core methods provided by the API to register and update custom scripts, the isolated execution environments used to keep browsing secure, and the user-level permissions required to activate these features.

Resolving the Manifest V3 Restriction

Under Manifest V3, Chromium banned the execution of remotely hosted code to improve browser security. Standard extensions can no longer fetch external JavaScript and execute it dynamically. While this stops malicious actors from sneaking unreviewed code past the Chrome Web Store, it fundamentally breaks user script managers like Tampermonkey and Violentmonkey. The primary purpose of these extensions is to run arbitrary, user-supplied code that is not bundled within the extension's original package. The chrome.userScripts API was engineered specifically to allow this functionality without compromising the broader security model of Manifest V3.

Registering Arbitrary Code

The core functionality of a script manager relies on the chrome.userScripts.register() method. Unlike the standard chrome.scripting API, which only allows executing static files packaged inside the extension, the User Scripts API accepts raw strings of code.

A script manager can take a script written or downloaded by a user, save it in the extension's storage, and pass it directly as a string using the js property during registration. The API also provides the following lifecycle methods:

  • getScripts(): Retrieves all currently registered user scripts.
  • update(): Modifies the code, URL match patterns, or execution timing of existing scripts without needing to unregister them first.
  • unregister(): Removes scripts when a user disables or deletes them in the manager's dashboard.

Dedicated Execution Worlds

To ensure untrusted user scripts do not compromise the extension itself, the API executes code in a specialized context called the USER_SCRIPT world. This environment is isolated from the extension’s high-privilege background service worker and standard content scripts, preventing a malicious user script from accessing powerful extension APIs directly.

Script managers can customize this environment using the chrome.userScripts.configureWorld() method. This allows the extension to perform two critical actions:

  1. Modify the Content Security Policy (CSP): The manager can relax the CSP for the USER_SCRIPT world, allowing user scripts to use functions like eval() if legacy scripts require them, without relaxing the security of the extension itself.
  2. Enable Messaging: By setting the messaging property to true, the isolated user script can securely communicate with the extension's background service worker. This is how script managers safely provide privileged functions (such as GM.xmlHttpRequest or GM.setValue) to the user's script.

If a user script needs to interact directly with the host webpage's JavaScript variables and functions, the API also gives the script manager the option to bypass the isolated world and inject the code directly into the webpage's MAIN world.

The Developer Mode Requirement

Because the User Scripts API reintroduces the ability to run unreviewed code, Chromium enforces a strict, user-facing security guardrail. Declaring the "userScripts" permission in the extension's manifest.json file is not enough to activate the API.

The chrome.userScripts API remains undefined and inaccessible until the user manually enables the "Developer mode" toggle on the browser's extensions management page (chrome://extensions). Script managers must detect if this API is available and guide the user to turn on Developer mode, ensuring that users explicitly opt into the elevated risks associated with running custom scripts.