Scripts can register buttons at runtime with OpenScript.button(label, onClick). The popup lists buttons from scripts running in the active tab via chrome.userScripts.execute in each script's world, and clicking one runs the handler in the page before closing the popup. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
OpenScript
A lightweight, modern script manager built for Chrome Manifest V3 with the native chrome.userScripts API.
| Empty state | Scripts | Secrets |
|---|---|---|
![]() |
![]() |
![]() |
Prerequisites
OpenScript requires Chrome 138 or newer. To run scripts:
- Install OpenScript from the Chrome Web Store.
- Open
chrome://extensions. - Select Details on the OpenScript extension card.
- Enable Allow User Scripts.
Writing scripts
Scripts use a small metadata block followed by ordinary JavaScript. OpenScript supplies the async wrapper, so top-level await, return, and isolated declarations work without boilerplate.
// ==UserScript==
// @name GitHub Repo Stats
// @version 1.0.0
// @description Logs repository metadata
// @match https://github.com/*
// @run-at document_idle
// ==/UserScript==
const [, owner, repo] = location.pathname.split('/');
if (!owner || !repo) return;
const response = await fetch(`https://api.github.com/repos/${owner}/${repo}`);
console.log(await response.json());
Metadata
| Directive | Description | Required |
|---|---|---|
@name |
Name displayed in the popup | Yes |
@version |
Version displayed beside the script name | No |
@match |
Chrome match pattern; repeat for multiple patterns | Yes |
@description |
Short summary displayed in the popup | No |
@run-at |
document_idle, document_start, or document_end |
No |
@require |
HTTP(S) library URL; repeat for multiple libraries | No |
The editor’s run-at selector is authoritative when a script is saved. Bare match URLs are normalized: github.com/* becomes *://github.com/*, and https://github.com becomes https://github.com/*.
Secrets and environment variables
Secrets saved in the popup are synchronized through chrome.storage.sync and exposed to every script through either namespace:
const token = OpenScript.env.GH_PAT;
const sameToken = env.GH_PAT;
Per-script storage
Every script gets isolated, persistent storage backed by chrome.storage.local:
await OpenScript.storage.set('repo_cache', { size: 1024 });
const cached = await OpenScript.storage.get('repo_cache'); // undefined when absent
const keys = await OpenScript.storage.list();
await OpenScript.storage.delete('repo_cache');
Stored keys are scoped to the current script and survive reloads and browser restarts. Orphaned values are removed when the popup opens after their script has been deleted.
Cross-origin network requests
Make cross-origin requests that bypass page CORS and CSP restrictions using the extension's privileged background worker:
const response = await OpenScript.fetch('https://api.example.com/data', {
headers: { Accept: 'application/json' },
credentials: 'include', // includes browser cookies for the target domain
});
const data = await response.json();
OpenScript.fetch(url, options) returns a native Response instance supporting .json(), .text(), .arrayBuffer(), .blob(), .status, .ok, and .headers. See the Cross-origin fetch guide for full options and examples.
Popup buttons
Scripts can add buttons under their entry in the popup's Scripts tab, for example to open a settings panel or trigger an action on the current page:
OpenScript.button('Settings', () => openSettingsPanel());
if (location.pathname.startsWith('/issues'))
OpenScript.button('Export issues', exportIssues);
const remove = OpenScript.button('One-time action', doThing);
remove(); // takes the button away again
Buttons are registered at runtime, so they show only for scripts running on the active tab. A script that matches *://*/* therefore shows its buttons on any regular web page, but not on pages where Chrome blocks user scripts, such as chrome:// pages and the Chrome Web Store. Clicking a button runs its handler inside the page in the script's world, then closes the popup.
External libraries
Use @require to cache libraries when a script is saved:
// ==UserScript==
// @name Alerts
// @match *://*/*
// @require https://cdn.jsdelivr.net/npm/sweetalert2@11
// ==/UserScript==
await Swal.fire('OpenScript is ready');
Libraries are prepended in declaration order inside the isolated USER_SCRIPT world. The cached source avoids page CSP restrictions and remains available offline. If a refresh fails, OpenScript uses the last cached copy; a script with a dependency that has never been cached is not registered.
Development
npm install
npm run dev
npm test
npm run build
npm run build:icons
npm run zip
Privacy
OpenScript does not track users or send analytics. Script code and per-script state stay in chrome.storage.local; secrets use chrome.storage.sync. URLs declared with @require are contacted only to download their requested libraries. See the Privacy Policy.


