A professional template for building userscripts with TypeScript, allowing you to write modular, type-safe, tested code that compiles into a single userscript file.
Transform your userscript development:
🎯 Type Safety - Catch bugs at compile-time, not in production. TypeScript prevents common errors before they reach users.
📦 Modularity - Split monolithic scripts into focused, reusable modules. No more scrolling through 1000+ line files.
🧪 Built-in Testing - Write tests with Vitest. Verify your code works before releasing to users.
🔍 IDE Superpowers - Full autocomplete for GM APIs, instant refactoring, and go-to-definition across your entire codebase.
🚀 Modern Tooling - ESLint, Prettier, Husky, and GitHub Actions already configured. Professional CI/CD out of the box.
⚡ Tree-Shaking - Automatically removes unused code. Import large libraries without bloating your userscript.
📚 Maintainability - Come back to your project months later and actually understand what you wrote. Clear structure + types = sustainable code.
Stop fighting with brittle monolithic JavaScript. Start building maintainable, professional userscripts.
Download the latest built userscript from the GitHub Releases page and install it in your userscript manager.
(Replace yourusername and typescript-userscript-template with your actual GitHub username and repository name)
Note:
dist/is gitignored — builds are attached as release artifacts, not committed to the repo.
Option 1: Use GitHub Template (Recommended)
- Click the "Use this template" button at the top of this repository
- Choose a name for your new repository
- Clone your new repository
- Run
npm run setup - Start coding!
Option 2: Clone Directly
git clone https://github.com/yourusername/typescript-userscript-template.git my-userscript
cd my-userscript
rm -rf .git # Remove template git history
git init # Start fresh
npm run setupRunning npm run setup launches an interactive wizard that configures the template for your project in one go:
- Asks for your userscript name, description, author, GitHub username, and repository name
- Confirms your inputs before making any changes — restarts if anything looks wrong
- Patches
package.json,meta.json, andREADME.mdwith your details - Sets
templateModetofalseso the CI/CD workflows behave correctly from the start - Runs
npm installautomatically - Removes template-specific files (
MIGRATION_GUIDE.md) - Removes itself — the setup script has no place in your actual project
After setup completes, everything is configured and ready to go.
- ✅ TypeScript Support - Write your userscripts with full TypeScript features and type checking
- ✅ Modular Code - Split your code into multiple files and modules
- ✅ Testing Framework - Vitest included for unit and integration tests
- ✅ Automatic Bundling - Rollup bundles everything into a single userscript file
- ✅ Tree-Shaking - Unused code is automatically removed from the final bundle
- ✅ GM API Support - Full TypeScript support for Tampermonkey/Greasemonkey APIs
- ✅ Userscript Metadata - Automatically inject userscript headers from
meta.json - ✅ Development Mode - Watch mode with inline sourcemaps for debugging
- ✅ Code Quality - ESLint + Prettier for consistent code style
- ✅ Pre-commit Hooks - Automatic validation before commits with Husky
- ✅ CI/CD - GitHub Actions for automated testing and releases
.
├── .github/
│ └── workflows/
│ ├── ci.yml # Continuous integration (lint, test, build)
│ ├── version-bump.yml # Bumps version and pushes tag
│ └── release.yml # Builds and publishes GitHub Release
├── scripts/
│ └── setup.js # One-time setup wizard (self-deletes after running)
├── src/
│ ├── index.ts # Main entry point
│ └── utils.ts # Utility functions (example)
├── tests/
│ ├── utils.test.ts # Example tests for utilities
│ └── index.test.ts # Example tests for main logic
├── dist/ # Gitignored — created by build
│ └── userscript.user.js # Built userscript (auto-generated)
├── meta.json # Userscript metadata
├── vitest.config.ts # Test configuration
├── package.json # Project dependencies
├── tsconfig.json # TypeScript configuration
├── rollup.config.js # Build configuration
├── eslint.config.js # ESLint configuration
├── .prettierrc # Prettier configuration
└── LICENSE # MIT License
npm run setupThis launches the interactive wizard, patches all files with your project details, and runs npm install automatically. See What does npm run setup do? for details.
Edit meta.json to customize your userscript metadata:
{
"name": "My TypeScript Userscript",
"namespace": "https://github.com/yourusername",
"version": "1.0.0",
"description": "A userscript built with TypeScript",
"author": "Your Name",
"match": [
"https://example.com/*"
],
"grant": [
"GM_addStyle",
"GM_getValue",
"GM_setValue"
],
"run-at": "document-end"
}Key fields:
match: URLs where your userscript runsgrant: GM API permissions your script needs (see Available GM APIs)run-at: When to run the script (document-start,document-end, ordocument-idle)connect: (Optional) Domains allowed forGM_xmlhttpRequestcross-origin requests. Only add if you useGM_xmlhttpRequest. Example:["api.example.com", "cdn.example.org"]version: This is your userscript version - what users see in Tampermonkey. Bump this when releasing updates. It is intentionally decoupled from the template version inpackage.json— the version-bump workflow automatically updates the correct file based on whethertemplateModeistrueorfalse.
Versioning strategy:
This depends on whether you're in template mode or userscript mode (set via "templateMode" in package.json):
package.json |
meta.json |
|
|---|---|---|
| Template mode | Template infrastructure version (bumped by workflow) | Placeholder 0.1.0 — never changed |
| Userscript mode | Not touched by workflow | Your userscript version (bumped by workflow, shown to users in Tampermonkey) |
The two versions are intentionally independent. In template mode the version-bump workflow runs npm version on package.json; in userscript mode it runs node scripts/update-meta-version.js on meta.json. Neither mode touches the other file.
Note: If you used
npm run setup,templateModeis already set tofalseandmeta.jsonis ready to track your userscript version. You don't need to touch this manually.
# Production build (optimized, no sourcemaps)
npm run build
# Development mode (watch mode with inline sourcemaps for debugging)
npm run devThe built userscript will be in dist/userscript.user.js.
-
Install a userscript manager extension:
- Tampermonkey (Chrome, Firefox, Safari, Edge)
- Violentmonkey (Chrome, Firefox, Edge)
- Greasemonkey (Firefox)
-
Open
dist/userscript.user.jsand copy its contents -
Create a new userscript in your userscript manager and paste the code
-
Write TypeScript code in the
src/directorysrc/index.tsis the main entry point- Create additional
.tsfiles as needed - Import/export modules as usual
-
Write tests for your code:
npm test # Run tests once npm run test:watch # Watch mode for test-driven development npm run test:ui # Interactive UI for exploring tests npm run test:coverage # Generate coverage report
-
Lint and format your code:
npm run lint # Check for errors npm run lint:fix # Auto-fix errors npm run format # Format code with Prettier
-
Run in watch mode during development:
npm run dev # Includes inline sourcemaps for debugging -
Test in browser:
- After each build, copy the updated
dist/userscript.user.jsto your userscript manager - Or set up automatic reloading (see Tips below)
- After each build, copy the updated
The template includes Vitest for testing. Write tests alongside your code:
// tests/utils.test.ts
import { describe, it, expect } from 'vitest';
import { log } from '../src/utils';
describe('utils', () => {
it('should log with prefix', () => {
// Your test logic here
});
});See the MIGRATION_GUIDE.md for more testing examples.
// src/index.ts
import { log, waitForElement } from './utils';
async function main(): Promise<void> {
log('Userscript started!');
try {
const element = await waitForElement('#my-element');
element.textContent = 'Modified by userscript!';
} catch (error) {
console.error('Error:', error);
}
}
// Start the userscript
// Note: With @run-at document-end, the DOM is already loaded
main();// src/utils.ts — log() and waitForElement() ship with the template
export function log(message: string): void {
console.log(`[UserScript] ${message}`);
}
// waitForElement polls with requestAnimationFrame and rejects
// after a configurable timeout (default 5 000 ms).
// See src/utils.ts for the full implementation.
// Using GM APIs
export function addStyles(css: string): void {
GM_addStyle(css);
}
export async function store(key: string, value: any): Promise<void> {
await GM_setValue(key, value);
}
export async function retrieve<T>(key: string, defaultValue?: T): Promise<T> {
return await GM_getValue(key, defaultValue);
}
export function notify(text: string, title?: string): void {
GM_notification({ text, title: title || 'UserScript' });
}The template includes TypeScript support for:
GM_addStyle(css)- Add CSS styles to the pageGM_getValue(key, default)- Get stored value (persistent across page loads)GM_setValue(key, value)- Store value persistentlyGM_deleteValue(key)- Delete stored valueGM_xmlhttpRequest(details)- Make cross-origin HTTP requestsGM_notification(details)- Show desktop notificationsGM_openInTab(url)- Open URL in new tabGM_setClipboard(text)- Copy text to clipboardGM_registerMenuCommand(name, fn)- Add menu command- And many more...
All functions have full TypeScript autocomplete and type checking!
-
GM API autocomplete: The
@types/tampermonkeypackage provides full TypeScript support. Just start typingGM_and VS Code will show available functions! -
Grant permissions: Always add the GM functions you use to the
grantarray in meta.json, otherwise they won't work -
Cross-origin requests: If you use
GM_xmlhttpRequestto make requests to external domains, add those specific domains to theconnectarray. Example:"connect": ["api.github.com", "cdn.example.com"]. Avoid using"*"wildcard for security reasons. -
Multiple userscripts: Duplicate this template folder for each userscript project
-
Source maps: Use
npm run devfor development builds with inline source maps to debug TypeScript in browser DevTools. Production builds (npm run build) exclude source maps for smaller file size. -
Tree-shaking: Unused exports are automatically removed from the bundle. Only code you actually import and use will be included
You can install and use npm packages:
npm install <package-name>Then import them in your TypeScript files:
import { someFunction } from 'package-name';Edit rollup.config.js to customize the build process:
- Change output format
- Add additional plugins
- Enable/disable source maps (set
sourcemap: truefor debugging) - etc.
Edit tsconfig.json to adjust TypeScript compiler options:
- Target ECMAScript version
- Strict mode settings
- Library inclusions
- etc.
The template includes ESLint and Prettier:
Configuration files:
eslint.config.js- ESLint rules (TypeScript-aware).prettierrc- Code formatting preferences.prettierignore- Files to skip formatting
Customization:
- Modify
eslint.config.jsto add/change linting rules - Update
.prettierrcfor different formatting preferences - Add GM globals to ESLint if you use additional GM functions
Users can install your userscript in multiple ways:
After creating a release (see Creating a Release), users can install from:
https://github.com/user/repo/releases/latest/download/userscript.user.js
Replace user/repo with your GitHub username and repository name.
Users can clone your repo and build manually:
git clone https://github.com/user/repo.git
cd repo
npm install
npm run build
# Install dist/userscript.user.js in Tampermonkey/GreasemonkeyFor development, you can use Tampermonkey's built-in editor or a local file:
npm run dev # Watch mode with sourcemaps
# Point Tampermonkey to file:///path/to/dist/userscript.user.jsThree workflows are included:
.github/workflows/ci.yml - Continuous Integration:
- Runs on every push and pull request
- Linting, formatting, type checking
- Runs test suite to catch bugs
- Grant validation and markdown link checks
- Builds the project to ensure everything works
- Ensures code quality and catches issues early
.github/workflows/version-bump.yml - Version Bumping:
- Manually triggered from the GitHub Actions UI
- Select patch/minor/major bump type
- Auto-detects mode from
package.json— no manual configuration needed - Updates
package.jsonin template mode ormeta.jsonin userscript mode, commits, and pushes av*tag - Guards against forgetting to update
repository.url
.github/workflows/release.yml - Release Publishing:
- Triggered automatically when a
v*tag is pushed (i.e. after every version bump) - Can also be triggered manually from the Actions UI — leave the tag field empty to release the latest tag, or specify an older tag to re-release a specific version
- Detects template vs userscript mode, builds the artifact if needed, and creates the GitHub Release
Version Bump (manual) → pushes v* tag → Release (automatic)
↑
Release (manual) ─────────────────┘
The release workflow is intentionally decoupled from the bump workflow. It triggers on any v* tag regardless of how the tag was created, which means you can also push a tag manually and get a release without going through the bump workflow.
⚠️ Always use the version-bump workflow to create tags. Manually pushed tags will causerelease.ymlto fail if the tag version does not match the version inpackage.json(template mode) ormeta.json(userscript mode). The error message will show both versions so you can fix the mismatch.
The workflows automatically detect how to behave based on package.json:
{
"userscript": {
"templateMode": true // ← set to false (or remove) when building a real userscript
}
}templateMode: true |
templateMode: false (or absent) |
|
|---|---|---|
| Updates | package.json only |
meta.json only |
| Release artifact | None | dist/userscript.user.js attached |
| Use case | Template/boilerplate maintainers | Userscript developers |
| Default | ✅ (ships with template) | Set when starting your userscript |
⚠️ Template mode releases have no build artifact. Sincemeta.jsonstays at0.1.0(the clean placeholder for users of this template), attaching the built file would show a version mismatch on the release. The release exists purely as a version marker. Once you settemplateMode: false, releases will include the built artifact as normal.
If you used npm run setup, templateMode is already false and everything is configured correctly.
- Go to Actions tab in your GitHub repository
- Select Version Bump workflow
- Click Run workflow
- Choose the version bump type:
- patch: 1.0.0 → 1.0.1 (bug fixes, minor updates)
- minor: 1.0.0 → 1.1.0 (new features)
- major: 1.0.0 → 2.0.0 (breaking changes)
- Click Run workflow
This automatically:
- Validates your
repository.urlis configured correctly - Detects template vs userscript mode
- Updates
package.json(template mode) ormeta.json(userscript mode) - Commits and pushes the version tag
- Triggers the Release workflow, which creates a GitHub Release with auto-generated release notes (and attaches the artifact in userscript mode)
Users can then install directly from the release:
https://github.com/user/repo/releases/latest/download/userscript.user.js
If the release workflow fails (e.g. a flaky runner or build error), the tag is already in place from the bump — you don't need to bump again. Just go to Actions → Release → Run workflow and leave the tag field empty to retry against the latest tag, or type a specific tag if needed.
To remove CI/CD: Simply delete the .github/workflows/ folder if you don't need it.
Note: This template is a starting point - most users heavily customize it. Updates are optional and typically only needed if you want new features from the template.
✅ Update if you want:
- New GitHub Actions workflows or improvements
- Better build/test configurations
- Security updates to tooling
❌ Don't update if:
- You've heavily customized configs
- Everything works fine for you
- You prefer stability over new features
Check the template repository for changes, then manually update:
.github/workflows/- CI/CD workflowseslint.config.js,tsconfig.json,.prettierrc- Linting/formattingrollup.config.js,vitest.config.ts- Build/test configpackage.json- Dependencies (or use Dependabot)
Use Dependabot (included) for automatic dependency updates, or:
npm update # Update to latest compatible versions
npm outdated # Check for major version updates- Run
npm installto ensure all dependencies are installed - Delete
node_modulesandpackage-lock.json, then runnpm installagain
- TypeScript handles GM_ type checking via
@types/tampermonkey - Add GM functions you use to
meta.jsongrants - Run
npm run check-grantsto validate
- Check that all GM functions are listed in
meta.jsongrants - Verify the
@matchpattern matches your target URLs - Check browser console for errors
- Ensure
@types/tampermonkeyis installed:npm install --save-dev @types/tampermonkey - Run
npm run type-checkto see all type errors - Check that your tsconfig.json is properly configured
- Run
npm run validateto check everything at once - Use
npm run lint:fixandnpm run formatto auto-fix issues
- Check that the tag was pushed successfully in the Version Bump workflow logs
- You can trigger the Release workflow manually from the Actions tab — leave the tag field empty to use the latest tag
Setup (one-time):
npm run setup- Interactive project setup wizard — configures all files and installs dependencies. Self-deletes after running.
Build:
npm run build- Build the userscript for production (no sourcemaps)npm run dev- Watch mode for development (with inline sourcemaps)
Testing:
npm test- Run tests oncenpm run test:watch- Watch mode for testsnpm run test:ui- Interactive test UI dashboardnpm run test:coverage- Generate coverage report
Code Quality:
npm run lint- Check for linting errorsnpm run lint:fix- Auto-fix linting errorsnpm run format- Format code with Prettiernpm run format:check- Check if code is formattednpm run type-check- Run TypeScript type checking
Validation:
npm run check-grants- Validate GM API grantsnpm run check-links- Check for broken links in markdown filesnpm run validate- Run all checks including tests (recommended before committing)
MIT
Feel free to customize this template for your needs!