The userscript manager that's actually yours.
OpenMonkey is a lightweight, open-source browser extension for running userscripts β built for people who are done handing their data to closed-source tools.
Tampermonkey β the standard for years. Closed source. Ships your data wherever it wants. You have no idea what it's doing in the background.
ViolentMonkey β open source, which is great. But there's still telemetry and data collection in the mix. "Open source" doesn't automatically mean "private."
OpenMonkey β built from scratch, owned by you, never uploaded to the Chrome Web Store, never phoning home. It lives on your disk, runs in your browser, and that's where it stays. No accounts. No analytics. No middleman.
- Run userscripts on any website matching
@matchpatterns - Full Greasemonkey/Tampermonkey-compatible script header format (
@name,@match,@exclude,@run-at,@description,@version) @run-at document-start/document-end/document-idleβ proper lifecycle hooks@excludepattern support- Per-script
@max-retriesβ built-in retry guard viasessionStorageto prevent infinite login loops or lockouts - Global default retry setting in the popup
- Enable / disable scripts per-script
- Full in-browser script editor (no external tools needed)
- Zero network requests. All storage is
chrome.storage.localβ never synced, never sent anywhere.
OpenMonkey is intentionally not on the Chrome Web Store. That's the point.
# 1. Clone the repo
git clone https://github.com/your-username/open-monkey.git
cd open-monkey
# 2. Install dependencies
pnpm install
# 3. Build
pnpm build
# 4. Load into Chrome
# Open chrome://extensions β Enable "Developer mode" β "Load unpacked" β select .output/chrome-mv3/For live development with hot-reload:
pnpm devWXT will automatically open Chrome with the extension loaded. Changes to any entrypoint or utility file rebuild and reload instantly.
OpenMonkey is built on WXT β the modern framework for browser extensions. It follows WXT's strict project layout conventions.
open-monkey/
βββ entrypoints/
β βββ background.ts # MV3 service worker β matches tabs β injects scripts
β βββ popup/
β βββ index.html # Popup HTML shell
β βββ main.tsx # React root mount
β βββ App.tsx # Script list + editor UI
β βββ App.css # Popup styles
β βββ style.css # Global reset/base
βββ utils/
β βββ storage.ts # Typed WXT storage items (scripts + settings)
β βββ meta-parser.ts # Parses ==UserScript== header blocks
β βββ match-pattern.ts # Chrome match-pattern URL matching
β βββ logger.ts # Dev-mode logger wrapper
βββ assets/ # Processed assets (imported in code)
βββ public/
β βββ icon/ # Extension icons (copied as-is to output)
βββ wxt.config.ts # WXT config β manifest options, modules
βββ web-ext.config.ts # Browser launch/dev config
βββ tsconfig.json # TypeScript config (generated by WXT)
βββ package.json
| Convention | What it means here |
|---|---|
entrypoints/background.ts |
Auto-registered as the MV3 service worker |
entrypoints/popup/ |
Popup entrypoint directory β index.html is the root |
utils/ |
Auto-imported by WXT β no import statements needed in most files |
public/ |
Static files copied verbatim to .output/ |
assets/ |
Processed by Vite β use for imported images, fonts, etc. |
defineBackground() |
WXT's entrypoint wrapper β keeps runtime code out of module scope |
storage.defineItem() |
@wxt-dev/storage typed, versioned storage items |
browser.* |
WXT's unified cross-browser API β works on Chrome and Firefox |
pnpm build β .output/chrome-mv3/ |
Standard WXT output directory |
OpenMonkey uses the standard Greasemonkey header format. Add scripts directly in the popup editor:
// ==UserScript==
// @name My Script
// @description Brief description of what it does
// @version 1.0.0
// @match https://example.com/*
// @exclude https://example.com/login
// @run-at document-end
// @max-retries 3
// ==/UserScript==
(function () {
'use strict';
// Your code here
})();| Directive | Description |
|---|---|
@name |
Script display name (required) |
@description |
Short description shown in the popup |
@version |
Semver version string |
@match |
URL pattern(s) to run on β supports Chrome match pattern syntax |
@exclude |
URL pattern(s) to explicitly skip |
@run-at |
document-start, document-end (default), or document-idle |
@max-retries |
Max injection attempts per tab session (overrides global setting) |
Multiple @match and @exclude lines are supported.
The background.ts service worker listens to browser.tabs.onUpdated. On each navigation event:
- Load all scripts from
chrome.storage.local - Parse each script's
==UserScript==header - Match the tab URL against
@match/@excludepatterns - Check
@run-atagainst the current navigation phase (loadingβdocument-start,completeβdocument-end) - Wrap the script body in a
sessionStorage-based retry guard (ifmaxRetries > 0) - Inject via
chrome.scripting.executeScriptwithworld: "MAIN"β full DOM access, same context as page JS
// Scripts run in the page's own JS context, not the extension sandbox
await chrome.scripting.executeScript({
target: { tabId },
func: (code: string) => {
const el = Object.assign(document.createElement('script'), { textContent: code });
document.head.append(el);
el.remove();
},
args: [codeToInject],
world: 'MAIN',
});This approach mirrors how Tampermonkey injects scripts and gives your userscripts the same DOM access they'd have in any other manager.
All data lives in chrome.storage.local. Nothing is ever synced or sent anywhere.
// Defined in utils/storage.ts using @wxt-dev/storage
export const scriptsItem = storage.defineItem<UserScript[]>('local:scripts', {
fallback: [],
version: 1,
});
export const settingsItem = storage.defineItem<Settings>('local:settings', {
fallback: { maxRetries: 3 },
version: 1,
});@wxt-dev/storage handles typed reads/writes, reactive .watch() subscriptions (used by the popup's React state), and migration hooks for future schema changes.
# Install deps
pnpm install
# Dev mode (Chrome, hot-reload)
pnpm dev
# Dev mode (Firefox)
pnpm dev:firefox
# Production build
pnpm build
# Production build for Firefox
pnpm build:firefox
# Zip for distribution (sideload/share)
pnpm zip
# TypeScript type check
pnpm compile- Never published to any store. Load unpacked, own it completely.
- No telemetry, no analytics, no remote config. The extension makes zero outbound requests.
chrome.storage.localonly. No sync storage. No IndexedDB. No server.- MV3 by default. Modern Manifest V3 with a proper service worker background.
- Self-hosted and version-controlled. Fork it, modify it, make it yours.
| Tool | Role |
|---|---|
| WXT | Browser extension framework β build, dev, manifest generation |
| React 19 | Popup UI |
| TypeScript | Everywhere |
@wxt-dev/storage |
Typed, versioned chrome.storage.local wrapper |
| pnpm | Package manager |
| Vite | Bundler (via WXT) |
- CodeMirror syntax highlighting in the editor
-
@requiredirective β load and cache external libraries - Per-script execution log / error display in popup
- Import/export scripts as
.user.jsfiles - Options page for advanced settings
- Firefox AMO sideload support (already builds with
pnpm build:firefox)
MIT. Do whatever you want with it. That's also the point.