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:
- Modify the Content Security Policy (CSP): The
manager can relax the CSP for the
USER_SCRIPTworld, allowing user scripts to use functions likeeval()if legacy scripts require them, without relaxing the security of the extension itself. - Enable Messaging: By setting the
messagingproperty totrue, the isolated user script can securely communicate with the extension's background service worker. This is how script managers safely provide privileged functions (such asGM.xmlHttpRequestorGM.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.