userext
v0.1.0
Published
Build Chrome, Firefox, and Safari Web Extensions from standard userscript .user.js files.
Maintainers
Readme
userext
userext builds Chrome, Firefox, and Safari Web Extension projects from ordinary userscript .user.js files.
npx userext init my-extension
cd my-extension
npx userext buildGenerated projects keep source userscripts in src/ and write browser outputs to dist/.
Stable extension identity material is retained under .userext/ so repeated releases can update the same extension instead of creating a new one.
Why
Userscripts are a pleasant way to write browser customizations, but release packaging becomes repetitive once you want Chrome, Firefox, and Safari builds. userext treats userscript headers as the source of truth, creates target manifests, copies script assets, retains IDs and keys, and produces release-ready folders and zip files.
Install
Use it directly:
npx userext init
npx userext buildOr install it in a project:
npm install --save-dev userext
npx userext buildCommands
userext init [dir]
Creates a minimal userscript-first project by default:
my-extension/
userext.config.json
src/
hello.user.js
.gitignore
README.mdIn an interactive terminal, init asks whether to add optional extension scaffolding. In non-interactive mode or with --yes, it keeps the project minimal unless you pass feature flags.
Create the full professional scaffold:
npx userext init my-extension --fullFull scaffold:
my-extension/
userext.config.json
src/
hello.user.js
assets/
icons/
icon-16.png
icon-32.png
icon-48.png
icon-96.png
icon-128.png
icon-256.png
public/
popup.html
options.html
userext-page.css
vendor/
README.md
_locales/
en/
messages.json
.gitignore
README.mdGranular init flags:
npx userext init --icons
npx userext init --public
npx userext init --vendor
npx userext init --locales
npx userext init --manifestuserext build
Reads every src/**/*.user.js file, parses each // ==UserScript== header, and writes:
dist/
chrome/
manifest.json
*.user.js
firefox/
manifest.json
*.user.js
safari/
manifest.json
SAFARI.md
my-extension-chrome-0.1.0.zip
my-extension-chrome-0.1.0.crx
my-extension-firefox-0.1.0.xpi
my-extension-safari-0.1.0-webextension.zip
.userext/
ids.json
keys/
chrome.pemChrome builds include a CRX3 package created with the retained .userext/keys/chrome.pem key. Firefox builds include an XPI package. Safari builds include a zipped Safari Web Extension source bundle; final App Store or Developer ID distribution still requires Apple's Xcode signing and packaging flow.
userext doctor
Checks the local project, Node version, source folder, and userscript metadata.
userext clean
Removes dist/ while keeping .userext/ so extension IDs and keys survive future builds.
Userscript metadata
userext reads standard userscript metadata:
// ==UserScript==
// @name Example Tools
// @namespace https://example.com
// @version 1.2.3
// @description Helpful browser tools.
// @match https://example.com/*
// @include https://*.example.org/*
// @exclude https://example.com/admin/*
// @run-at document-idle
// @grant GM_getValue
// @grant GM_setValue
// ==/UserScript==Supported metadata today:
@name,@namespace,@version,@description,@author@match,@include,@exclude@run-at,@noframes@grantfor storage helpers:GM_getValue,GM_setValue,GM_deleteValue,GM_listValues- local
@icon,@iconURL,@resource, and local@require
Remote @require is detected and reported, but not downloaded during build.
Project Folders
userext treats the project layout as part of the build contract. Only src/ is required; the other folders are optional and can be created by init --full, granular init flags, or by hand:
src/: one or more*.user.jsuserscripts.assets/: copied todist/<target>/assets; put extension images, CSS, and other bundled assets here.assets/icons/: scanned for icon files named likeicon-16.png,icon-48.png, oricon-128.png.public/: copied to the extension root; use it forpopup.html,options.html, or other manifest-facing pages.vendor/: copied todist/<target>/vendor; local@requirefiles are also copied undervendor/userscript/<script-id>/._locales/: copied to the extension root as WebExtension locale files.
Icons
Generated projects include PNG icons at common extension sizes. You can replace them with your own files and wire them explicitly:
{
"icons": {
"16": "icons/icon-16.png",
"32": "icons/icon-32.png",
"48": "icons/icon-48.png",
"128": "icons/icon-128.png"
}
}Icon paths are resolved from assets/, so icons/icon-128.png becomes assets/icons/icon-128.png in the final extension. If icons is omitted, userext discovers files in assets/icons/.
Config
userext.config.json:
{
"name": "Example Tools",
"version": "0.1.0",
"description": "Browser tools built from userscripts.",
"author": "",
"homepageUrl": "",
"defaultLocale": "en",
"sourceDir": "src",
"assetsDir": "assets",
"publicDir": "public",
"vendorDir": "vendor",
"localesDir": "_locales",
"outDir": "dist",
"stateDir": ".userext",
"icons": {
"16": "icons/icon-16.png",
"32": "icons/icon-32.png",
"48": "icons/icon-48.png",
"128": "icons/icon-128.png"
},
"targets": ["chrome", "firefox", "safari"],
"browserDefaults": {
"permissions": ["storage"],
"hostPermissions": []
},
"manifest": {
"action": {
"default_title": "Example Tools",
"default_popup": "popup.html"
},
"options_ui": {
"page": "options.html",
"open_in_tab": true
}
},
"targetOverrides": {
"firefox": {
"manifest": {
"browser_specific_settings": {
"gecko": {
"strict_min_version": "109.0"
}
}
}
}
}
}The manifest object is deep-merged into every generated manifest. targetOverrides.<target>.manifest is then deep-merged for browser-specific settings.
Build one target:
npx userext build --target firefoxUse another working directory:
npx userext build --cwd ./my-extensionLifecycle
- Create a project with
npx userext init. - Add or edit
src/*.user.js. - Run
npx userext doctor. - Run
npx userext build. - Test unpacked folders:
- Chrome: load
dist/chromeas an unpacked extension. - Firefox: load
dist/firefoxfromabout:debugging. - Safari: import or wrap
dist/safariwith Xcode's Safari Web Extension tooling.
- Chrome: load
- Release package artifacts from
dist/*.crx,dist/*.xpi, anddist/*-webextension.zip. - Keep
.userext/ids.jsonand.userext/keys/chrome.pemprivate and backed up.
Project status
This is an early open-source implementation. It intentionally starts dependency-light and transparent:
- No runtime npm dependencies.
- Plain Node.js build pipeline.
- Unpacked browser extension folders for local testing.
- CRX3 packages for Chrome, XPI packages for Firefox, and zipped Safari Web Extension source bundles.
- Retained extension identity state.
- First-class extension assets, icons, public pages, vendor files, locales, and manifest overrides.
- A root
SKILL.mdfor developer agents working on userext projects.
License
Released under the Unlicense.
