npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

to-userscript

v0.1.4

Published

Converts simple browser extensions to userscripts

Readme

to-userscript

2x Speed (conversion takes 4s total normal speed, incl. downloading)

https://github.com/user-attachments/assets/e0adebcb-843d-4b04-830b-0b6ef5344652

Demo but normal speed

https://github.com/user-attachments/assets/874dc6fd-ad6c-4e07-9d27-da1184f3036d

A powerful CLI for converting browser extensions into standalone userscripts.

Examples:

| Extension | View output code | Install | | ---------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------- | | Material Design File Icons | View | Install | | JSON Formatter | View | Install | | Modern for Wikipedia | View | Install | | Return Dislikes | View | Install | | Web Search Navigator | View | Install | | uBlacklist | View | Install | | Iridium | View | Install | | Modern for Hackernews | View | Install |

What is this?

to-userscript converts browser extensions into portable userscripts. It takes an extension from the Chrome or Firefox store, a local directory, or a zip/xpi, and creates a single .user.js file that can be run in any userscript manager. This allows you to use, or modify extensions on browsers or platforms that might not natively support them.

Features

  • Convert from Chrome Web Store, Firefox Add-ons site, direct URLs, local directories, or archive files (.crx, .xpi, .zip).
  • Replaces WebExtension APIs (chrome.storage, chrome.runtime, chrome.tabs, etc.) with userscript-compatible equivalents (GM_* functions, IndexedDB, and custom event buses).
  • Asset inlining: Embeds CSS, images, fonts, and other resources into the userscript (replacing runtime.getURL and options/popup pages).
  • Renders extension popup and options pages within an embedded modal (after inlining all asset links), preserving most UI functionality.
  • Emulates the background script environment, allowing persistent logic to run as intended and two way messaging.
  • Respects _locales directories and uses the specified or default locale for manifest, text, etc.
  • Optional minification (terser) and code formatting (prettier) for the final output.

Installation

to-userscript requires Node.js v16 or higher.

You can install it globally using your preferred package manager:

# npm
npm install -g to-userscript
# pnpm
pnpm add -g to-userscript
# bun
bun install -g to-userscript

Alternatively, you can run it directly without a global installation using npx, pnpm dlx, or bunx.

Polyfill status:

Supported APIs

  • [x] chrome.storage
    • [x] local (backed by GM_* storage or IndexedDB)
    • [x] sync (maps to local storage)
    • [x] managed (shimmed; read-only and empty)
    • [x] onChanged
  • [x] chrome.runtime
    • [x] sendMessage/onMessage (in-page/iframe communication only)
    • [x] getURL (for bundled assets only)
    • [x] getManifest
    • [x] openOptionsPage: Opens options popup
  • [x] chrome.tabs
    • [x] create (maps to GM_openInTab or window.open)
    • [x] query (shimmed; returns current tab only)
    • [x] sendMessage (shimmed; redirects to runtime.sendMessage)
  • [x] chrome.i18n
    • [x] getMessage
    • [x] getUILanguage
  • [x] chrome.contextMenus (emulated via GM_registerMenuCommand; limited functionality)
  • [x] chrome.permissions
    • [x] request/contains/getAll (shimmed)
  • [x] chrome.notifications (maps to native Web Notifications API)
  • [x] chrome.cookies
    • [x] get
    • [x] getAll
    • [x] set
    • [x] remove
    • [x] getAllCookieStores
    • [x] getPartitionKey

Planned APIs

  • [ ] chrome.browserAction
  • [ ] chrome.tabs.insertCSS
  • [ ] chrome.tabs.reload
  • [ ] chrome.tabs.onActivated
  • [ ] chrome.scripting.insertCSS
  • [ ] chrome.scripting.executeScript
  • [ ] chrome.identity:
    • [ ] getAuthToken
    • [ ] getProfileUserInfo
    • [ ] clearAllCachedAuthTokens
  • [ ] chrome.runtime.onMessageExternal: Shim
  • [ ] chrome.runtime.sendMessageExternal: Shim
  • [ ] chrome.webNavigation
  • [ ] (partial) chrome.permissions:
    • Get listeners working
    • Removing permission for an origin should persist this change and not match the script on it
  • [ ] (mostly implemented) chrome.storage: Proper listeners
  • [ ] chrome.action
    • onClicked.addListener: Add menu item for click
  • [ ] Keyboard shortcuts (from manifest)
  • [ ] background.page support

General tasks

  • [ ] Make vanilla target better and more capable
  • [ ] API bindings: import makeUserscript from 'to-userscript';
    • [ ] Vite plugin
    • [ ] Take dir and output
  • [ ] Create reusable listener logic for supporting callbacks/promises

Usage & Commands

convert

Converts an extension into a userscript. This is the primary command.

Syntax: to-userscript convert <source> [options]

<source> Types:

  • URL: A Chrome Web Store or Firefox Add-ons URL.
  • Direct URL: A direct link to a .crx, .xpi, or .zip file.
  • Local Directory: A path to an unpacked extension directory.
  • Local Archive: A path to a local .crx, .xpi, or .zip file.

| Option | Alias | Description | | :---------------- | :---- | :-------------------------------------------------------------------------------------- | | --output | -o | Specify the output .user.js file path. | | --target | -t | Set build target. userscript (default) includes metadata, vanilla outputs plain JS. | | --minify | | Minify the JavaScript output using terser. | | --beautify | | Beautify the JavaScript output using prettier. | | --locale | -l | Preferred locale for name/description (e.g., en, fr, de). | | --ignore-assets | | Asset extensions to ignore during inlining (e.g., mp4,webm,ttf). | | --force | -f | Overwrite the output file if it exists. | | --keep-temp | | Keep temporary files for debugging purposes. |

Examples

1. Convert from the Chrome Web Store with minification:

to-userscript convert "https://chromewebstore.google.com/detail/modern-for-wikipedia/emdkdnnopdnajipoapepbeeiemahbjcn?hl=en" -o modern-wikipedia.user.js --minify

2. Convert a local directory with French localization:

to-userscript convert ./my-unpacked-extension/ -o my-script.user.js --locale fr

3. Convert a downloaded XPI to vanilla JS (using pnpm dlx):

pnpm dlx to-userscript convert ./my-addon.xpi --target vanilla -o my-addon.js

download

Syntax: to-userscript download <source>

Example

# Using bunx to download Material Design File Icons
bunx to-userscript download "https://chromewebstore.google.com/detail/material-icons-for-github/bggfcpfjbdkhfhfmkjpbhnkhnpjjeomc/" -o ublock-origin.xpi

require

Generates a metadata block to @require another local userscript.

Syntax: to-userscript require <path-to-userscript>

Example

# Generate a main script that @requires the converted script
to-userscript require ./material-design-fileicons.user.js | pbcopy

How It Works

For a detailed explanation of the internal conversion pipeline, see the Architecture Guide note: AI generated but proofread.

What it can (and can't/shouldn't) convert

  • Extensions intended for one site should work well, extensions meant to apply some sort of enhancement to the entire web might not work quite as well
  • Extensions where the functionality is primarily browser API based (even though these are polyfilled most of the time it's not something useful for the functionality)
  • E.g: An adblocker is a bad use case for this, as is something like a custom new tab page or similar, whereas an extension like "YouTube auto HD" might work really well

Troubleshooting & Advanced Usage

Content Security Policy (CSP) Issues

Some websites have strict CSP rules that can prevent converted userscripts from functioning correctly (blocking data urls and blobs). If your script isn't working as expected on a specific site, you may need to adjust Tampermonkey's security settings.

  1. In Tampermonkey, go to the Dashboard.
  2. Click the Settings tab.
  3. Change Config mode from "Beginner" to "Advanced".
  4. Scroll down to the Security section.
  5. For the option Modify existing Content Security headers, select "Remove entirely".

Resulting userscript output format:

// ==UserScript==
// ... Metadata Block ...
// ==/UserScript==

(function () {
  // IIFE for scope isolation
  "use strict";

  // 1. UNIFIED POLYFILL is defined here
  //    - messaging.template.js -> createEventBus, createRuntime
  //    - abstractionLayer.*.template.js -> _storageSet, _fetch, etc.
  //    - assetsGenerator code -> EXTENSION_ASSETS_MAP, _createAssetUrl
  //    - polyfill.template.js -> buildPolyfill() which creates chrome.*

  // 2. BACKGROUND SCRIPT ENVIRONMENT is defined and executed
  //    - Runs all background scripts inside the polyfill's scope.
  //    - This happens immediately on script start.

  // 3. ORCHESTRATION LOGIC is defined and executed
  //    - Checks if location.href matches a content_script pattern.
  //    - If it matches:
  //        - Calls `executeAllScripts()`.
  //        - This function injects CSS and runs JS in phases:
  //          - document-start
  //          - document-end
  //          - document-idle
  //    - Registers GM_registerMenuCommand for options/popup pages.
  //    - Options/Popup pages are rendered in a modal with an iframe.
  //    - The iframe's content is populated with the inlined HTML and
  //      a specialized 'postmessage' version of the polyfill.
})();

Contributing

I welcome contributions! Especially to implement new chrome apis, or fix bugs.