Introduction
FireMonkey is a totally new combined userscript and userstyle manager. While it has similar functions to other userscript managers like Greasemonkey/Tampermonkey/Violentmonkey, and userstyle managers like Stylish/Stylus/xStyle, there are also some differences. Similar to other userscript managers, scripts (or CSS) are injected in tabs when tabs are reloaded.
Compatibility
| Area | Details | Exposure |
|---|---|---|
| UserScript | ||
| uncommon @grant 1 | GM_getTab, GM_getTabs, GM_saveTab |
55 / 63,435 (0.08%) scripts use these APIs |
window.close, window.focus, window.onurlchange |
483 / 63,435 (0.76%) scripts use these APIs Probably a lot less as there would be duplicates |
|
| invalid Match Patterns | Violentmonkey accepts http*, www.google.*, and .tld (invalid match patterns are rejected by the browser API) See: Quick Fixes |
1,935 / 125,246 (1%) use http* 334 / 125,246 (0.2%) use site.*/ |
| UserStyle | ||
| @var range @var number @var select |
Must be valid JSON
|
|
| @var checkbox | Requires JavaScript to process logical true/false which is not supported by Firefox yet See also: if() |
|
| @preprocessor less | Requires Less JavaScript Library | |
| @preprocessor stylus | Stylus uses JavaScript or features which are not part of CSS | |
1 Many userscripts declare @grant for GM APIs that they don't use.
Quick Fixes
| Issue | Try | |
|---|---|---|
| Script/CSS needs to run in sub-frames | Try setting @allFrames true in User MetadataSee also: defaults |
|
No @run-at and script runs late |
Try setting @run-at document-end in User MetadataSee also: defaults |
|
window.addEventListener does not fire'load/DOMContentLoaded'
|
Try setting @run-at document-end in User MetadataSee also: defaults |
|
| restricted domains | Script injection are blocked by Firefox on restricted domains See: restricted domains |
|
| Content Security Policy (CSP) | unsafeWindow or page context injection on pages with strict CSP that may only work with Tampermonkey due to CSP relaxationSee: CSP Exclude |
|
| Trusted Types API and jQuery 1-3 (Firefox 148) |
TypeError: Element.innerHTML setter: Sink type mismatch violation blocked by CSPTry jQuery 4, or workaround |
|
| invalid Match Patterns (Violentmonkey) |
change | to |
@match http*://www.example.com/... |
@match *://www.example.com/... |
|
@match https://www.example.*/...@match https://www.example.tld/...
|
@include https://www.example.*/... |
|
Manifest V3 (planned for FireMonkey 4)
| Issue | Details |
|---|---|
| userScripts.execute() | added in Firefox 153 (released 2026-07-21) |
| GM registerMenuCommand GM addValueChangeListener |
runtime.onUserScriptMessage added in Firefox 136 (released 2025-03-04) |
| GM info.isIncognito | No support at the moment |
| GM xmlHttpRequest (Chrome only) |
No support for setting Cookie, Host, Origin, Referer at the moment on Chrome |
No support for background execution (no CORS) at the moment on Chrome
|
|
File access becomes opt-in (Firefox 153)
Extensions requesting file://*/ or <all_urls> currently trigger the “Access your data for all websites” permission message, and when granted, can run content scripts in file:-URLs. From Firefox 153, file access in extensions requires an opt-in for all extensions, including those already installed (bug 2034168). ⓘ
Light/Dark Theme
Theme is based on the browser's default light/dark theme.
Firefox on Android
Support is experimental and based on user feedback.
How to Use
Toolbar Icon
- Badge
- Badge indicates the number of Script/CSS that are active in the tab (and all its iframes)
- Title
- Displays the active Script/CSS on mouse-over
Toolbar Context Menu (not available on Android)
- Options
- Same as Toolbar Icon Pop-up
- Scripts
- Open the Options page ➔ Scripts Tab
- New UserScript - UserCSS
- Same as Toolbar Icon Pop-up
- Help
- Same as Toolbar Icon Pop-up
- Log
- Display the latest (default 100) error and script-update messages
- Log data is not available in Container/Private mode
Toolbar Popup
The pop-up displays the installed Scripts/CSS. "Tab Scripts" shows the ones running on the active Tab. The number in the "Tab Scripts" shows the number of frames of the page. The minimum is 1 which is the main/top frame.
Click to hide/show all enabled Script and CSS
Click to hide/show all disabled Script and CSS
Click to hide/show all Scripts
Click to hide/show all CSS
🡘
- Toggle enable/disable
- Disabled Script/CSS are greyed out
- Shows that there was an error when registering the script and the Information page displays the error
- script name
- Shows the Information for the Script/CSS
- Displays homepage link if script has
@homepage,@homepageURL,@updateURL,@website, or@source - Displays support link if script has
@support, or@supportURL -
- 🖉 Edit
- Button on Information page opens the Options Page and selects the current Script/CSS
- ▷ Run
- Inject the displayed userScript or userCSS (not userStyle) as long as it was not already injected in the tab
-
UserScript is injected into
contentcontext which has more privilege thanuserScriptcontext and untrusted code should not be used.
The userScript is injected as plain JavaScript. Its metadata block would not be processed and it has no access to GM API. - ◁ Undo
- Undo/Remove the displayed userCSS if it was injected temporarily by
▷ Run
Options
- Open the Options Page
- Open the Options page and display a new Script Template that you can edit and Save
- New scripts are not enabled, to prevent registering incomplete scripts when saving while editing
- See also: Script Template
Help
- Display the Help document
Scratchpad
- Insert CSS and JavaScript temporarily into the active tab
- Scratchpad recalls the last entries for repeated use (not available in private mode)
- Remove the entry to delete
-
- origin
- Set the CSS @origin (defaults to author)
- ▷ Run
- Insert the Scratchpad code (JavaScript or CSS)
- JavaScript is injected into
contentcontext which has more privilege thanuserScriptcontext and untrusted code should not be used. - ◁ Undo
- Undo/Remove the CSS Inserted by the Scratchpad
Script Commands
- Show available Script Commands, click on each to run
Find scripts for this site
- Search for scripts for this site
Sidebar Editor (not available on Android)
Sidebar mini-editor for quick editing. You can hide/show the Script List with F11.
Keyboard Shortcut (not available on Android)
- Set keyboard shortcuts
- Click the menu button ☰, click
Add-ons and Themesand selectExtensions, or go to:about:addons - Click the
Tools for all add-onscogwheel - Click
Manage Extension Shortcutsin the menu - You will see the shortcut options (if available) for your installed add-ons
- See also: Manage extension shortcuts in Firefox
Script/CSS Installation
Scripts can be directly installed from any script ending in .user.js/.user.css (remote or local file system) if loaded into a tab (open or drag and drop). In case of HTTP/S links, the same link will be set for script update. (v2.57)
See also: Update and Auto-Update
Options
Auto-Update Interval
You can set the number of days between updates. Setting 0 (default) means there will be no auto-update. In order to minimise the impact on your browsing, auto-update is set to run when Firefox is idle and then 10 updates at a time (until the next idle time).
Updating each script can take around 4-5 seconds. If there are many scripts, that can add up to consider amount of time. It is recommended to manually update Scripts/CSS or enable auto-update only on the ones that require regular update.
Enable Sync
Enable sync storage to sync data between different profiles (maximum 100 KB)
If synching fails, there will be a notification and Sync will be turned off automatically to avoid repeated errors.
- If enabled, sync data will be sent when Options are saved
- At the moment, storage.sync is not supported on Firefox for Android
| Maximum total size | 102,400 bytes (100 KB) |
| Maximum item size | 8,192 bytes (8 KB) |
| Maximum number of items | 512 |
See also
Counter
Enable/Disable Script/CSS counter (Toolbar Icon Badge and Toolbar Icon Title)
Global Exclude
Exclude Match pattens (one per line) to prevent all scripts (not CSS) from running on them. Changes to Global Exclude will unregister all running script. New settings will take effect on new tab and on tab refresh.
CSP Exclude (v3.0)
Exclude Match pattens (one per line) to enable scripts/CSS injection in pages with restrictive Content Security Policy (CSP).
Users should avoid using CSP Exclude unless absolutely necessary, and then, limit the match patterns to the absolute minimum.
Editor Options (advanced users)
Customize Monaco Editor Global or Variable Editor Options with a valid JSON.
{
"tabSize": 4,
"minimap": {"enabled": true}
}
Custom Editor Theme
Monaco Editor includes "vs" (default), "vs-dark", and "hc-black" built-in themes. FireMonkey automatically switches between "vs" and "vs-dark" based on the system light/dark scheme.
to be implemented
Linter Options (advanced users)
Set or override default ESLint Rules (Playground) with a valid JSON. Default rules (ESLint recommended + FireMonkey additions) are set to a fairly strict guidelines for coding excellence. "Active Rules" displays the currently applied rules and will get updated according to the user rules.
{
"eqeqeq": "off",
"no-var": "off",
"prefer-const": "off"
}
Custom Options CSS (advanced users)
Customize Options page and editor style/theme with valid CSS.
Changes can break the layout.
Custom Popup CSS (advanced users)
Customize Toolbar Popup style/theme with valid CSS.
Changes can break the layout.
Settings: Import/Export
You can import/export preferences (for backup or sharing) from/to a local file on your computer.
Import from URL will fetch preference JSON from a remote server. The URL is then saved temporarily in localStorage for reuse.
Since v2.25, import will over-write options and scripts with the same name but keeps other scripts. Import is additive which means entries from an even incomplete preferences will be added.
Import is non-destructive. Click Save to apply the changes.
It is better to avoid importing pre-2.25 back-ups.
Save
Please note that changes will not take effect until they are saved.
Script and CSS
Sidebar
- Script List
- Lists both UserScript and UserCSS
- Scripts and CSS have different icons, and disabled ones are grey
- Click on the Script/CSS to view/edit/enable/disable/delete
- shows that there was an error when registering the script
- Same as Toolbar Icon Pop-up
- Counter
- Shows the number of Script and CSS
- Import
- Import one or more Script/CSS from file
- Script/CSS with the same name will be overwritten without warning
-
- Import from Stylus
- Export styles from Stylus and Import the JSON (on pop-up change the file type selection to All Files)
- Script/CSS with the same name will be overwritten without warning
- See also: UserStyle
- Export
- Export one or multi-selected Script/CSS
- Ctrl click button to export all
- Export is made to the download location set in Firefox Options ➔ Downloads
- Multiple export is disabled on Android
- Multi Select (for Enable, Delete and Export)
- Ctrl or Shift click Script List to select multiple items
Editor
Monaco Editor with built-in IntelliSense is used for easier editing.
Monaco Editor does not resize very well when resizing the window.
| FireMonkey (3.0) |
Greasemonkey (4.13) |
Tampermonkey (5.4.1) |
Violentmonkey (2.31.0) |
ScriptCat (0.16.15) |
Stylus (2.3.18) |
|
|---|---|---|---|---|---|---|
| editor | Monaco Editor (0.55.1) | CodeMirror | CodeMirror | CodeMirror | Monaco Editor (0.52.2) | CodeMirror |
| linter | IntelliSense ESLint (10.7.0) |
n/a | ESLint (8.32.0) | n/a | IntelliSense ESLint (7.32.0) |
Stylelint |
| light/dark theme |
Editor Buttons
Editing is non-destructive. Click Save to apply the changes.
Settings
-
- User Metadata
- Persistent user metadata rules to add/amend/disable original Script and CSS metadata rules
- You can use the select to quickly enter values
// Disable Rule // without a value disables ALL original metadata rules of that key // with a value disables SINGLE original metadata rule of that key @disable-container @disable-exclude @disable-exclude-match @disable-include @disable-match // Add/Amend Rule changes existing metadata rules @allFrames @connect @container @exclude-match @group @inject-into @match @matchAboutBlank @metaURL @origin @run-at @updateURL @uploadURL
- Storage
- View/Edit userscript storage
- Storage value must be a valid JSON or nothing (v2.66)
- User Variables
- Reset will revert to default values after Save
- Save
- Save the currently displaying Script/CSS
- Script is trimmed and trailing Spaces are removed automatically when saving
Update
- Update the currently displaying Script/CSS
- Diff Viewer is shown if there is a successful update
- Installing an older version from GreasyFork will register that version's update URL and therefore the script will not update to a new version
Upload
- Upload script to
@uploadURLvalue (if set) - Enable
- Enable/Disable the currently displaying Script/CSS (or multi-selected)
- Changes apply immediately without the need to Save
Auto-update
- Enable/Disable the Auto-Update of the currently displaying Script/CSS
- Changes apply immediately without the need to Save
- Display a new Script Template that you can edit and Save
- New script/CSS starts disabled
Delete
- Delete the currently displaying Script/CSS after confirmation (or multi-selected)
Editor Context Menu
- Tidy the Metadata Block
- Remove leading spaces on each line
- Set one space between
// @ - Align the entry values in the Metadata Block
- Set one empty line after the Metadata Block
- Convert to Match
- Convert
@include/@excludeto@match/@exclude-matchwherever possible - Convert to UserCSS
- Conversion requirements:
- Only one segment
- No
regex() - No
@var checkbox - No
@preprosessor less|stylus
- Save as Template
- Save the currently displaying Script/CSS as a Template
| Description | Keyboard Command |
|---|---|
| Save (FireMonkey) | Ctrl + S |
| Toggle fullscreen (FireMonkey) | F11 |
| Exit fullscreen (FireMonkey) | Esc |
| Toggle word wrap (FireMonkey) | Alt + Z |
| Command Palette | F1 |
| Cut | Ctrl + X |
| Copy | Ctrl + C |
| Paste | Ctrl + V |
| Undo | Ctrl + Z |
| Redo | Ctrl + Y |
| Search | Ctrl + F |
| Replace | Ctrl + H |
| Show hint | Ctrl + Space |
| Format Document | Shift + Alt + F |
| Toggle line comment | Ctrl + / |
| Toggle block Comment | Ctrl + Shift + A |
| Increase indent | Tab or Ctrl + ] |
| Decrease indent | Shift + Tab or Ctrl + [ |
| Move a line down | Alt + Down |
| Move a line up | Alt + Up |
| Select all | Ctrl + A |
| Select downward | Shift + Down |
| Select right | Shift + Right |
| Select left | Shift + Left |
| Select upward | Shift + Up |
Linter
ESLint provides extensive JavaScript linting, in addition to Monaco Editor's IntelliSense. In order to prevent unwanted load, ESLint is set to run when a userscript is loaded, and with a slight delay after making edits (ignoring whitespace edits).
Script Template
Any Script/CSS can be saved as a Template which will be shown when clicking New script/CSS. Supported URL based variables will be replaced with their corresponding values from URL object when New script/CSS clicked from the toolbar context menu, toolbar Popup, or sidebar.
// ==UserScript==
// @name {{hostname}}
// @match {{origin}}/*
// @version {{date}}
// @container {{container}}
// ==/UserScript==
Script Template variables are not unified among userscript managers.
| FireMonkey | Greasemonkey | Tampermonkey | Violentmonkey | ScriptCat | |
|---|---|---|---|---|---|
| supported |
{{container}} {{date}} (2026-02-27) {{hash}} {{host}} {{hostname}} {{href}} {{origin}} {{pathname}} {{port}} {{protocol}} {{search}} |
<$DATE$> (2026-02-27) <$ICON$> (href) <$URL$> |
{{date}} (27/02/2026, 14:10:57) {{date:format}} {{icon}} {{name}} (hostname) {{url}} (href) |
{{icon}} {{match}} |
Self-Update
For users that edit scripts regularly, a proper editor is more convenient. It is not possible to directly edit the userscripts with an external editor, however, you can try the following:
- External Editors
- Export the userscript to the computer
- Open the exported userscript with the desired editor
- Make the necessary edits and save
- Loading into browser
- Copy and Paste directly into the script Editor
- Import from Navigation Buttons
- Drag and Drop to a tab (or open in tab) and confirm to install
In case of repeated edit, you can keep the tab open and refresh and confirm to update the old one - Local Server
- Install a minimal HTTP server to serve the script
- Set the target in
@updateURL - WebDAV
- Set the target in
@uploadURL
Update and Auto-Update
Any target can be set for @updateURL as long it directly leads to the final file, and it is in plain text. The .user.js or .user.css file extensions are not necessary, although it makes sense to have them.
FireMonkey uses @version for the update process and both the current file and the update file must have @version and the version of the target file must be higher.
- In case of GreasyFork/SleazyFork/OpenUserJS, the
@versionviahttps://...../*.meta.js(orhttps://...../*.meta.css) is checked first - Update will be canceled if the new version has a different name which already exists among user's installed Scripts/CSS
@downloadURL/@installURL, if present, is set for script update@metaURL, if present, is set for script update check (v2.68)- If
@downloadURL, ends in.meta.js, it will be set as@metaURL(v2.68) - Install URL will be set as update URL if there is no
@updateURL/@downloadURLin the metadata block (v2.57)
meta.js/meta.css
FireMonkey only checks @version, therefore a minimal meta.js/meta.css would suffice.
@version 1.0
Metadata Block
FireMonkey supports the following entries based on the Firefox API.
- The Metadata Block identifiers are case-insensitive
- userScript:
==UserScript== ... ==/UserScript== - userCSS:
==UserCSS== ... ==/UserCSS== - userStyle:
==UserStyle== ... ==/UserStyle==
- userScript:
- For clarity, it is recommended to have the Metadata Block on top
- Each entry must be in the format of
@key ... space/s ... value - In case of @match (and @exclude-match, @include, ...), repeat for each value
- Any entry can be added for reference or information (e.g. @homepage, @copyright, @support, ...)
Multi-word key naming is not uniformed.
| userscript manager | browser | ||||
|---|---|---|---|---|---|
| joined lowercase | kebab case | camel case (with URL) |
camel case (match API) |
snake case (manifest) |
camel case (API) |
|
@noframes @antifeature @defaulticon |
@exclude-match @inject-into @run-at @top-level-await |
@downloadURL @homepageURL @icon64URL @iconURL @supportURL |
Tampermonkey @webRequest FireMonkey @allFrames @matchAboutBlank |
all_frames css_origin exclude_globs exclude_matches include_globs match_about_blank match_origin_as_fallback run_at |
allFrames cssOrigin excludeGlobs excludeMatches includeGlobs matchAboutBlank matchOriginAsFallback runAt |
1 Icons (@icon, @icon64, ...) are not processed since remote icons can be used to track users.
// ==UserScript== // @name My Script // @description Scripting is fun // @match *://*.example.com/* // @version 1.0 // ==/UserScript==
/* ==UserCSS== @name My CSS @description Styling is fun @match *://*.example.com/* @version 1.0 ==/UserCSS== */
defaults
FireMonkey conforms to the Firefox and Chrome userScripts and content_scripts API defaults. In most cases, it would be sufficient and prevents unnecessary overheads of injecting into sub-frames needlessly. However, Captcha, Disqus, etc are usually included as iframes, therefore, userscript dealing with them should be set to inject into all frames.
| FireMonkey | Greasemonkey | Tampermonkey | Violentmonkey | ScriptCat | Stylus | |
|---|---|---|---|---|---|---|
| @run-at | document-idle (JS) document-start (CSS) |
document-end | document-idle | document-end ⓘ | document-idle | document-start |
| @allFrames | false | true | true | true | true | true |
| @matchAboutBlank | false | @include about:blank | false | false | false | true |
URL Matching
Match Patterns are used in @match / @exclude-match and globs in @include / @exclude.
A glob is just a string that may contain wildcards. There are two types of wildcard, and you can combine them in the same glob:
- Wildcard * matches zero or more characters
- Character ? matches exactly one character
For example: "*na?i" would match "illuminati" and "annunaki", but not "sagnarelli".
Paths are case-sensitive.
Due to the processing logic in Firefox and Chrome in MV2, when using include (includeGlob), there must also be the mandatory match.
Since
matchesis the only mandatory key, the other three keys are used to limit further the URLs that match. To match the key as a whole, a URL must:content_scripts
- match the
matchesproperty- AND match the
include_globsproperty, if present- AND NOT match the
exclude_matchesproperty, if present- AND NOT match the
exclude_globsproperty, if present
Having mixed @match/@exclude-match with @include/@exclude may have unexpected results.
| @include | @match |
|---|---|
| * | *://*/* |
| http://* | http://*/* |
| https://* | https://*/* |
| http*://* | *://*/* |
| http*://a.b.c/* | *://a.b.c/* |
| *.example.com/* | *://*.example.com/* |
📊 URL Matching Performance
From best (top) to worst:
- match and exclude-match (match patterns) (userScripts/userCSS/userStyle)
- include and exclude (glob patterns) (userScripts/userCSS/userStyle)
- exclude with regular expression (userScripts)
- include with regular expression (userScripts/userStyle)
Restricted domains
Script injection are blocked by Firefox on Restricted domains (such as addons.mozilla.org).
It can be enabled in about:addons ➔ FireMonkey ➔ Details ➔ Run on sites with restrictions
See also: Why are some add-ons not allowed on sites restricted by Mozilla?
@antifeature
Under-development feature on GreasyFork to mark possibly undesirable behaviour e.g. ads, miners, etc.
Statistics show 632 of 126,449 scripts (0.04%) on Greasy Fork use @antifeature.
Anti-Features are flags packagers apply to apps, warning of possibly undesirable behaviour from the user's perspective, often serving the interest of the developer or a third party.
AntiFeatures
@connect (v3.0)
Limit the domains (includes subdomains) that GM download, GM fetch, and GM xmlHttpRequest can access
- One entry per line
- No wildcard support
selfallows thehostnameof the page the script is running in
// @connect example.com // @connect help.example.com // @connect 1.2.3.4 // @connect localhost // @connect co.uk // @connect self
@container (not available on Android)
Setting one or more entries will further limit the script/CSS to certain contextual identity containers e.g. default|private|container-1|container-2|... after URL matching.
- no @container: inject into all tabs
default: inject only into non-private, non-container tabs-
private: inject only into private Tabs'private' only works if user has allowed the extension to run in private mode.
container-N: inject only into Firefox container-N tabs
@container default @container private @container container-1
@grant (breaking change in v3.0)
- GM APIs are granted by
@grant(v3.0) - "GM info" is available without
@grant @grant noneand having no@grantare treated the same way@grant nonewill clear all other@grantentries- No
@grantwill result in injection into the page (v3.0) - Due to processing differences, including both quasi-synchronous
GM_*and asynchronousGM.*storage APIs in the@grantis not recommended
🛡️ Examples of Security Risks
// @resource remoteCode http://www.someSite.com/bad-js.js // @resource remoteDom http://www.someSite.com/html-with-bad-js.html const js = GM_getResourceText('remoteCode'); eval(js); const dom = GM_getResourceText('remoteDom'); element.innerHTML = dom;
@group (v3.0)
You can use @group to create a group master & sub-group under a userScript or userCSS.
- Both userScripts and userCSS can be listed under
@group - A script with or without any URL matching can be used as the master controller
@groupcan be set directly in script/CSS or User Metadata- Group action is applied on saved data
- One entry per line
- Similar to
@require, value is the script name - Scripts listed under
@groupcan be enabled/disabled individually - Enabling or disabling the group master will be reflected on the group, overriding their individual states
- Scripts with
@groupare marked on script lists
// ==UserScript== // @name My Script // @description Scripting is fun // @match *://*.example.com/* // @version 1.0 // @group userScript name 1 // @group userScript name 2 // @group userCss name 1 // @group userCss name 2 // ==/UserScript== // some code
@include / @exclude
@include/@exclude are the old and error-prone method of matching which has been superseded by @match/@exclude-match.
The
Greasemonkey @match@matchmetadata imperative is very similar to@include, however it is safer. It sets more strict rules on what the*character means.
It is recommended to use
Violentmonkey Matching@match/@exclude-matchrather than@include/@excludebecause the match rules are safer and more strict.
Regular Expression in @include / @exclude
Regular Expression support has been implemented for @include / @exclude (v2.5).
- Regular Expressions start and end with forward-slash e.g.
/http://test\.com/.+/ - Regular Expressions are processed with new RegExp()
- No need for escaping forward-slashes
- Regular Expressions are set as case-insensitive
Statistics show that only a small percentage of userscripts use regular expression in @include and @exclude.
| total | use regex | percentage | |
|---|---|---|---|
| scripts/styles | 113,151 | 2,973 | 2.6% |
| install count | 211,144,364 | 6,556,394 | 3.1% |
@inject-into
Setting the value page will inject the entire userscript into the page context. It is also automatically affected by @grant and @unwrap.
See also: MV2 Injection Context Comparison
- Only applies to userscripts
- Only
pageis supported - In
pagemode, userscript injection might get blocked by page's Content Security Policy (CSP) - In
pagemode, userscripts have no access to GM functions however ...GM.info|GM_infois available to userscripts for Tampermonkey/Violentmonkey compatibilityunsafeWindowis available to userscripts for Violentmonkey compatibility
@match / @exclude-match
| Pattern | match | no-match |
|---|---|---|
*://*/*Match all http, https |
http://example.com/ https://www.example.com/aaa/ |
file:///blah/* |
*://*.example.com/*
|
http://example.com/ https://www.example.com/ http://www.sub.example.com/aaa/ |
http://example.org/ |
*://example.com/
|
http://example.com/ https://example.com/ |
https://www.example.com/ http://example.com/aaa/ |
https://*/path
|
https://www.example.com/path |
http://example.com/ http://example.com/path |
file:///blah/*
|
file:///blah/ file:///blah/etc |
file:///etc/ |
| Invalid Pattern | Reason | |
| resource://path/ | Unsupported scheme | |
| https://mozilla.org | No path | |
| https://mozilla.*.org/ | "*" in host must be at the start | |
| https://*zilla.org/ | "*" in host must be the only character or be followed by "." | |
| http*://mozilla.org/ | "*" in scheme must be the only character | |
| https://mozilla.org:80/ | Host must not include a port number | |
| *://* | Empty path: this should be "*://*/*" | |
| file://* | Empty path: this should be "file:///*" | |
@matchAboutBlank
matchAboutBlank Optional
boolean. Iftrue, the code will be injected into embeddedabout:blankandabout:srcdocframes if your extension has access to their parent document. The code cannot be inserted in top-levelabout:frames.Defaults to
tabs.executeScript()false.
@name
FireMonkey uses the name (case-sensitive) as ID for Scripts and CSS, therefore names must be unique. A shorter and concise name is recommended.
Localization
Some entries can be localized for display propose e.g. @name:zh-CN, @description:kr. The language code is case-sensitive and must match Language Tags and Locale Identifiers e.g. "en", "en-US", "de", "fr", etc.
Entries without local identifier can also match navigator.language with local identifier. For example, if navigator.language is "en-US" it will match name:en-US or name:en.
@origin (v3.0)
CSS inserted by the browser API by default does not apply to shadow DOM. Setting origin1 to user would apply the CSS to shadow DOM as well, but note its affects on the Cascading order.
- Only applies to userCSS
- Available in Toolbar Popup ➔ Run, and Scratchpad
- Available in registered userCSS since Firefox 144
- Only
useris supported (defaults to 'author') - Use when needed for shadow DOMs
- Use
!importantif the style is overridden by the page
1 MV2 browser API used cssOrigin, but it is changed to origin in MV3.
@require
Multiple @require entries are injected in order.
Required userscripts will only have access to GM functions that are granted in the primary userscript.
| @require | UserScript | UserCSS |
|---|---|---|
| installed userScript (by name) | ||
| remote JS | ||
| installed userCSS (by name) | ||
| remote CSS | (URL ending with .css) |
@require script-name
@require can be used to pre-include other existing scripts into a script (or other existing CSS into a CSS). For example, user/developer can save some code as a script and name it HelperSet. It does not need to have match/exclude-match/include/exclude etc. It is best to have the script disabled.
Script Example
// ==UserScript== // @name HelperSet // @description A set of helper functions // @version 1.0 // ==/UserScript== function someFunc(id) { // some code } function otherFunc(text) { // some code }
Include in Other Scripts
// ==UserScript== // @name My Script // @description Scripting is fun // @match *://*.example.com/* // @match *://*.example.org/* // @version 1.0 // @require HelperSet // ==/UserScript== // some code
CSS require
/* ==UserCSS== @name My CSS @description CSS is fun @match *://*.example.com/* @match *://*.example.org/* @version 1.0 @require DefaultCSS @require https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css ==/UserCSS== */
@require URL
The API works slightly differently in comparison with other script managers as the target is not not stored. It uses fetch() to get the target the first time but on subsequently Request.cache will be used by the browser. Consequently, the latest version of the file will always be fetched. Please note that processing many scripts with many @require can take time when registering scripts (i.e. browser start-up, disabling and re-enabling Script/CSS and/or FireMonkey).
You can also use @import in userCSS for remote CSS.
FireMonkey injects remote CSS using @import which may be affected by the site's CSP.
CSS require Alternative
/* ==UserCSS== @name My CSS @description CSS is fun @match *://*.example.com/* @match *://*.example.org/* @version 1.0 ==/UserCSS== */ @import 'https://fonts.googleapis.com/css?family=Tangerine'; /* --- or --- */ @import url('https://fonts.googleapis.com/css?family=Tangerine'); body { font-family: 'Tangerine', serif; font-size: 48px; }
Storing @require and @resource Targets
In legacy Firefox, extensions were able to save files to their folder in Firefox profile. Script managers would download @require and @resource targets, save it to users' HD and inject it when called. Since Firefox 57 (2017-11-14), it no longer allows extension to save files to Firefox profile folder. Script managers will have to use the storage assigned to the extension to save @require and @resource files in form of data.
Saving large volume of data to storage impacts RAM and CPU resource usage of the extension, slows down read/write to the extension storage and generally affects the browser performance. Known Libraries often are 100+ KB to 1+ MB e.g. jquery-3.7.1.js (278 KB), jquery-3.7.1.min.js (85 KB), jquery-ui-1.12.1.js (508 KB), jquery-ui-1.12.1.min.js (247 KB), angular-1.8.3.js (1.3 MB) ... etc. In case of @resource, images for example could be many megabytes of data.
Considering that a user may have many userscripts and they may have many @require and @resource, the size of storage can get extremely large and its effect on browser performance will become quite considerable.
@run-at
FireMonkey's default run-at is document-idle for Scripts, therefore there is no need for event listeners such as 'load' or 'DOMContentLoaded' as they may not apply. If script has to run earlier, then the run-at has to be set accordingly.
UserScript managers use Kebab case, while the browser API uses Snake case. FireMonkey converts the hyphen, so both can be used.
FireMonkey and Firefox API run-at states directly correspond to Document.readyState.
| document-start | Corresponds to loading. The DOM is still loading.
GM addElement/addScript/addStyle and |
| document-end | Corresponds to interactive. The DOM has finished loading, but resources such as scripts, images, stylesheets and frames may still be loading.The state indicates that the DOMContentLoaded event is about to fire. |
| document-idle | Corresponds to complete. The document and all its resources have finished loading.The state indicates that the load event is about to fire. |
Tampermonkey supports additional non-standard run-at.
|
|
| document-body | The script will be injected if the body element exists. (Tampermonkey/Violentmonkey) |
| context-menu | The script will be injected if it is clicked at the browser context menu (desktop Chrome-based browsers only). Note: all @include and @exclude statements will be ignored if this value is used, but this may change in the future. |
@unwrap (v3.0)
Injects userScript without any GM API support in the page context in a UserScript Wrapper
@uploadURL
Any upload URL, with/without credentials, can be set to upload a script to a server using fetch(url, {method: 'PUT'})
@uploadURL http://username:password@localhost:8080/
@var
Configurable user variables for userScript and userCSS (one per line)
Values are saved according to JavaScript types e.g. boolean, number, and string. Number strings are converted to number.
@var can be used as userScript and userCSS configuration page.
| name | No white space | ||
| label | Must be quoted if contains white spaces | ||
| type | text | Display a text input | |
| number | Display a number input, valid JSON array of [default, min, max, step, unit]
|
||
| range | Range input is an alternative representation of a number input (same rules as number) |
||
| checkbox | Display a checkbox input, value of 0/1 (not suitable for useCSS) |
||
| color | Display a color input, value of 3/6-digit hex code Alpha values are maintained but not customizable in HTML5 Color Picker |
||
| select | Default option can be indicated by adding an asterisk * to the end of the key name, otherwise it will be the first option
|
/* Do not include the keyword !important in the variable value */ /* format (all on a single line) */ @var type name label value /* implementation in userScript/userStyle */ const name = value; /* implementation in userCSS */ :root { --name: value; } /* use in userCSS */ body { color: var(--name); } /* Example */ @var text fontSize "Font Size" 2em @var text bgImg "Bg Image" url('http://example.com/bkgd.jpg') @var text border Border 1px solid gray @var text logoImg "Logo Image" none @var checkbox enable Enabled 1 @var color fontColor "Font Color" #ff7700 @var number imgOpacity "Image Opacity" [0.5, 0, 1, 0.1] @var range imgHeight "Max Image Height" [50, 10, 200, 10, "px"] @var select fontName "Font Name" ["Arial", "Helvetica*", "\"Times New Roman\""] @var select headerBg "Header Background Color" {"Orange": "#ffa500", "Skyblue*": "#87ceeb"}
See also: Using CSS custom properties (variables)
Script API
FireMonkey supports both GM3 (GM_*) and GM4 (GM.*) (released 2017-11-02) style APIs. Many GM_* APIs are mapped to their GM.* counterparts and therefore identical. It is recommended to use GM4 API.
All checks are performed on Firefox.
| API | FireMonkey (3.0) |
Greasemonkey (4.13) |
Tampermonkey (5.4.1) |
Violentmonkey (2.31.0) |
ScriptCat (0.16.15) |
Stats1 |
|---|---|---|---|---|---|---|
| Storage | ||||||
| GM.getValue | v2.12.0 | 739 | ||||
| GM.getValues | v2.68, v3.0 | v5.3.0 | v2.19.1 | |||
| GM.setValue | 727 | |||||
| GM.setValues | v2.68, v3.0 | v5.3.0 | v2.19.1 | |||
| GM.deleteValue | 208 | |||||
| GM.deleteValues | v2.68, v3.0 | v5.3.0 | v2.19.1 | |||
| GM.listValues | 77 | |||||
| GM.addValueChangeListener | (undocumented) | 10 | ||||
| GM.removeValueChangeListener | (undocumented) | 1 | ||||
| DOM | ||||||
| GM.addElement | v4.12 | v2.13.1 | 4 | |||
| GM.addScript | 0 | |||||
| GM.addStyle | 102 | |||||
| GM.popup | 0 | |||||
| Connect | ||||||
| GM.download | v2.18.3 | 18 | ||||
| GM.fetch | 0 | |||||
| GM.xmlHttpRequest | v3.0 | v2.18.3 | 699 | |||
| Other | ||||||
| GM.getResourceText | (undocumented) | 11 | ||||
| GM.getResourceUrl | (different) | v2.13.1 | 30 | |||
| GM.info | 69 | |||||
| GM.log | (undocumented) | (not working) | 7 | |||
| GM.notification | 42 | |||||
| GM.openInTab | 178 | |||||
| GM.setClipboard | 77 | |||||
| GM.registerMenuCommand | v4.11 | v2.12.10 | 131 | |||
| GM.unregisterMenuCommand | 6 | |||||
| GM.cookie | v3.0 | (not working) | 9 | |||
| GM.getTab 2 | (not working) | 3 | ||||
| GM.getTabs 2 | (not working) | 0 | ||||
| GM.saveTab 2 | ||||||
| GM.audio | ||||||
| Storage | ||||||
| GM_getValue | 5,837 | |||||
| GM_getValues | v2.68, v3.0 | v5.3.0 | v2.19.1 | |||
| GM_setValue | 3,782 | |||||
| GM_setValues | v2.68, v3.0 | v5.3.0 | v2.19.1 | |||
| GM_deleteValue | 1,577 | |||||
| GM_deleteValues | v2.68, v3.0 | v5.3.0 | v2.19.1 | |||
| GM_listValues | 711 | |||||
| GM_addValueChangeListener | (mapped to GM.*) | 209 | ||||
| GM_removeValueChangeListener | (mapped to GM.*) | 77 | ||||
| DOM | ||||||
| GM_addElement | (mapped to GM.*) | v4.11 | v2.13.1 | 41 | ||
| GM_addScript | (mapped to GM.*) | 0 | ||||
| GM_addStyle | (mapped to GM.*) | 5,452 | ||||
| GM_popup | (mapped to GM.*) | 0 | ||||
| Connect | ||||||
| GM_download | (mapped to GM.*) | 478 | ||||
| GM_fetch (deprecated v3.4) | (mapped to GM.*) | 3 | ||||
| GM_xmlhttpRequest | 5,436 | |||||
| Other | ||||||
| GM_getResourceText | (mapped to GM.*) | 954 | ||||
| GM_getResourceURL | (mapped to GM.*) | 355 | ||||
| GM_info | (mapped to GM.*) | 731 | ||||
| GM_log | (mapped to GM.*) | (not working) | 865 | |||
| GM_notification | (mapped to GM.*) | 617 | ||||
| GM_openInTab | (mapped to GM.*) | 1,326 | ||||
| GM_setClipboard | (mapped to GM.*) | 1,556 | ||||
| GM_registerMenuCommand | (mapped to GM.*) | 1,923 | ||||
| GM_unregisterMenuCommand | (mapped to GM.*) | 220 | ||||
| GM_cookie | (not working) | 49 | ||||
| GM_getTab 2 | 43 | |||||
| GM_getTabs 2 | 39 | |||||
| GM_saveTab 2 | 37 | |||||
| GM_audio | ||||||
| unsafeWindow |
userScript page |
content |
content page |
content page |
content | 3,057 |
| window.close 3 | limited support | v2.6.2 | 279 | |||
| window.focus 4 | v2.12.10 | 135 | ||||
| window.onurlchange | v4.11 | 69 | ||||
| window.external 5 | v2.74 | v4.2 | v2.12.8 | |||
| Injected Scripts ✔ every page ✔ every frame ✔ when no active userscript |
injected-web.js (17KB) injected.js (15KB) |
src/content.js (143KB) | ||||
| log matches the userscript line (not in page) |
||||||
| Open Source |
Proprietary License EULA | minified | minified | |||
| No Data Collection | Privacy policy | Privacy policy | ||||
| Data Collection Permissions (manifest.json) |
none | Popup: Please enable anonymous statistics and help optimize this extension. |
Optional: • Share authentication information with extension developer • Share personally identifying information with extension developer |
|||
| Tracking | none | favicon (Google, DuckDuckGo, Native) |
||||
| CSP workaround | user defined (v3.0) | auto (changeable) | (removes all) | |||
| Firefox Users | 2k | 173k | 870k | 130k | 9k | |
| Last Update | 2025 | 2024 | 2025 | 2025 | 2025 | |
1 GreasyFork 2022-02-22 from 63,465 userscripts
2 Primarily, userscripts are meant to run within the context of a single tab. getTab|getTabs|saveTab would allow userscripts to track users across open tabs and through navigations, without users' knowledge. While userscripts can also use the script storage to track users across tabs, script storage is less convenient for tracking and it can be inspected by the user. Userscript can also use Window.localStorage and Window.sessionStorage for this purpose.
3 window.close circumvents the global window.close() browser safeguard, and allows a userscript to close a tab that it has not opened. It would allow malware userscripts to run code and hide the result by closing the tab before the user has had a chance to notice. The choice to close a tab opened by the user, should remain with the user. In FireMonkey, tabs that have been opened with GM.openInTab can be closed with JavaScript window.close() (v2.45).
4 window.focus circumvents the global window.focus(). It could be disruptive to users who are in the middle of a game or other activity in another tab, if a userscript in the background causes its tab to become active and come to the foreground. The choice to focus a tab, should remain with the user.
5 Safer synchronous window.external for GreasyFork, only provides the version number for the script page that the user is on.
JavaScript Context Layers
Contexts (scopes) are sandboxed layers of JavaScript in an extension to ensure security.
| Context | Browser API Access | Details |
|---|---|---|
| browser | all | Trusted privileged code to interact with the browser |
| content (contentScript) | some | Trusted extension's own JavaScript injected into a web page with some browser API privileges (there is only one content context per extension per frame) |
| userScript | only GM API | Untrusted unverified 3rd party JavaScript injected into a web page without direct browser API privileges (there can be many isolated userScript contexts) |
| page | none | JavaScript injected in a web page (there is only one page context per frame) |
| Type | Browser API Access | Details |
|---|---|---|
| browser | all | Trusted privileged code to interact with the browser |
| ISOLATED (content) | some | The default execution environment of content scripts. This environment is isolated from the page's context: while they share the same document, the global scopes and available APIs differ. |
| USER_SCRIPT | only GM API | Details to be finalised runtime.sendMessage(), runtime.connect() See also: User Scripts API |
| MAIN (page) | none | The web page's execution environment. This environment is shared with the web page without isolation.
Scripts in this environment don't have any access to the APIs that are only available to content scripts.
Due to the lack of isolation, the web page can detect the executed code and interfere with it.
Do not use the |
Userscripts injected in content context, carry increased risks.
1 Userscripts in FireMonkey are by default injected into a separate isolated userScript contexts, and therefore there is no interaction between the userscripts. In content or page context, the userscripts will share the same context which can result in unexpected behaviour e.g.
- Scripts running on the same page share the same window wrapper
- [Firefox] Scripts share the same window wrapper if `@inject-into content` is used
2 Content Security Policy
Add-ons must not relax web page security headers, such as the Content Security Policy.
It is also worth noting that the hack with removing the CSP can cause a conflict with other addons that use CSP to block content (like uBlock Origin)
.- Tampermonkey should not modify CSP on websites where it is disabled
- Circumvent TrustedHTML errors
- Possible to allow running inlined wasm just like JS despite CSP
- Can't modify page properties on sites which use CSP
| FireMonkey | Greasemonkey | Tampermonkey | Violentmonkey | ScriptCat | |
|---|---|---|---|---|---|
| default | ISOLATED | ISOLATED | ? | ||
| @grant *** | MAIN ? | MAIN ? | ? | ||
| @grant none (no @grant) | MAIN ? | MAIN ? | ? |
UserScript Wrapper (v3.0)
Greasemonkey, Tampermonkey and Violentmonkey (as well as Node.js), wrap userscripts in an IIFE which avoids JavaScript syntax errors such as SyntaxError: return not in function.
For better compatibility, userscripts are wrapped in an async IIFE before injection which enables:
- return out of function
- Top-level await
Linter will evaluate the code in strict mode
| FireMonkey | Greasemonkey | Tampermonkey | Violentmonkey | ScriptCat | |
|---|---|---|---|---|---|
| return out of function | |||||
| Top-level await | via @top-level-await | ||||
| with @unwrap |
Dynamic import()
JavaScript ECMAScript 2020 (ES11) dynamic import() can be used in a userscript.
Examples of importing jQuery (UMD module)
// importing jQuery as global variable import('https://cdnjs.cloudflare.com/ajax/libs/jquery/3.7.0/jquery.slim.min.js') .then(() => { // jQuery is only available in this block statement jQuery('<div>Hello, World!</div>').appendTo('body'); }); // ... or ... (async () => { await import('https://cdnjs.cloudflare.com/ajax/libs/jquery/3.7.0/jquery.slim.min.js'); // jQuery is only available in this block statement jQuery('<div>Hello, World!</div>').appendTo('body'); })();
import() expects JavaScript MIME type e.g. 'text/javascript'. CDN providers usually set the proper headers, however, some sites may not, which results in an error.
| content-type header | |
|---|---|
| application/javascript |
https://cdnjs.cloudflare.com/ajax/libs/jquery/3.7.0/jquery.slim.min.js https://code.jquery.com/jquery-3.7.0.slim.min.js https://www.unpkg.com/react@16.7.0/umd/react.production.min.js |
| text/javascript | https://ajax.googleapis.com/ajax/libs/jquery/3.7.0/jquery.min.js |
| text/plain | https://raw.githubusercontent.com/erosman/psl/main/psl.js
TypeError: error loading dynamically imported module Loading module from “https://raw.githubusercontent.com/erosman/psl/main/psl.js” was blocked because of a disallowed MIME type (“text/plain”). |
| CORS |
Cross-Origin Request Blocked: The Same Origin Policy disallows reading the remote resource. |
| CSP |
Content-Security-Policy: The page's settings blocked the loading of a resource at https://cdnjs.cloudflare.com/ajax/libs/jquery/3.7.0/jquery.slim.min.js (“script-src”). |
| Get the text |
// @resource psl https://raw.githubusercontent.com/erosman/psl/main/psl.js // @grant GM_getResourceText // Get the module as text const psl = GM_getResourceText('psl');... or ... const response = await GM.fetch('https://raw.githubusercontent.com/erosman/psl/main/psl.js'); const psl = await response.text();... or ... const response = await GM.xmlHttpRequest({ url:'https://raw.githubusercontent.com/erosman/psl/main/psl.js' }); const psl = response.responseText; |
| Convert and import |
// convert to Data URL const url = "data:text/javascript," + psl; // import variables must match module exports const {PSL} = await import(url);... or ... // Create object URL from the text const blob = new Blob([psl], {type: 'text/javascript'}); const url = URL.createObjectURL(blob); // import variables must match module exports const {PSL} = await import(url); // Optionally release the object URL for optimal performance and memory usage URL.revokeObjectURL(url); |
Script Storage
| FireMonkey | Greasemonkey | Tampermonkey | Violentmonkey | ScriptCat | |
|---|---|---|---|---|---|
| UserScript Storage | extension storage | IndexedDB | extension storage | extension storage | IndexedDB |
| FireMonkey | Stylus | Stylish | xStyle | ||
| UserStyle Storage | extension storage | IndexedDB | IndexedDB | IndexedDB | |
| Private | Public | |
|---|---|---|
| Asynchronous |
Extension storage API IndexedDB |
IndexedDB (shared with web page domain) |
| Synchronous |
Web storage API on privileged pages localStorage | sessionStorage (background, options, popup etc, but not in contentScript, userScript, and private windows) |
Web storage API on web pages localStorage | sessionStorage (shared with web page domain) |
Quasi-synchronous Storage APIs (v2.43)
Quasi-synchronous GM_* storage feature has been added to improve compatibility with older userScripts. It delays the userscript execution until synchronous storage is available (~5-10ms). The recommended asynchronous GM.getValue and GM.listValues will get the most up-to-date values at any moment.
Due to processing differences, including both quasi-synchronous GM_* and asynchronous GM.* storage APIs in the @grant is not recommended.
GM.getValue
A key (string) with optional default value
Return value: value (or default value)
const value = await GM.getValue(key [, default]); // only cached storage is retrieved synchronously const value = GM_getValue(key [, default]);
GM.getValues (v2.68, v3.0)
Keys (an array of strings, or an object specifying default values) to identify the item(s) to be retrieved from storage. If you pass an empty object or array here, an empty object will be retrieved. If you pass null, or an undefined value, the entire storage contents will be retrieved.
Return value: key/value object
const values = await GM.getValues(); // only cached storage is retrieved synchronously const values = GM_getValues();
// get the entire storage const storage = await GM.getValues(); // return {key: value, key2: value2, ...} // get values for array of keys const values = await GM.getValues([key, key2, ...]); // return {key: value, key2: value2, ...} // get values for object with default values const values = await GM.getValues({key: default, key2: default2, ...}); // return {key: value || default, key2: value2 || default2, ...}
GM.setValue
A key/value pair
Return value: undefined
// value can be string, number, boolean, or object await GM.setValue(key, value); // only cached storage is updated synchronously GM_setValue(key, value);
GM.setValues (v2.68, v3.0)
An object containing one or more key/value pairs to be stored in storage. If an item already exists, its value will be updated.
Return value: undefined
await GM.setValues({key: value, key2: value2, ...}); // only cached storage is updated synchronously GM_setValues({key: value, key2: value2, ...});
GM.deleteValue
A key (string)
Return value: undefined
await GM.deleteValue(key); // only cached storage is updated synchronously GM_deleteValue(key);
GM.deleteValues (v2.68, v3.0)
An array of strings, representing the key(s) of the item(s) to be removed.
Return value: undefined
await GM.deleteValues([key, key2, ...]); // only cached storage is updated synchronously GM_deleteValues([key, key2, ...]);
GM.listValues
no argument
Return value: array
const keys = await GM.listValues(); // only cached storage is retrieved synchronously const keys = GM_listValues();
GM.addValueChangeListener
Script storage change listener for changes from other tabs. In FireMonkey 3.0, storage changes do not trigger the listener in the same tab.
Return value: listener ID (= key)
// note: listenerId is the same as key in FireMonkey const listenerId = GM.addValueChangeListener(key, callback); GM.addValueChangeListener(key, callback); GM_addValueChangeListener(key, callback);
(key, oldValue, newValue, remote) are passed to the callback function.
- key: the storage key
- oldValue: original value or
undefinedif it was created - newValue: new value or or
undefinedif it was deleted - remote:
trueif change came from another tab orfalseif from the same tab (always true in FireMonkey 3.0)
// anonymous function GM.addValueChangeListener('test-key', function(...arg) { console.log(...arg); }); // anonymous arrow function GM.addValueChangeListener('test-key', (key, oldValue, newValue, remote) => { console.log(key, oldValue, newValue, remote); });
GM.removeValueChangeListener
Remove listener for a key
Return value: undefined
GM.removeValueChangeListener(key); GM_removeValueChangeListener(key);
GM.addElement
Utility function compatibility API with Tampermonkey and Violentmonkey .
Return value: created element || undefined (on error or for scripts)
const elem = GM.addElement([parentNode,] tagName, attributes); const elem = GM_addElement([parentNode,] tagName, attributes);
| parentNode (optional) | Any node/element or shadowRoot If omitted, it will be set as
|
| tagName (string) | Any valid HTML tag |
| attributes (object) | Object with any valid attribute and/or textContent|innerText|innerHTML |
// loading an external script const elem = GM.addElement('script', {src: 'https://....'}); elem.onload = () => console.log(elem, 'loaded'); // appending to shadowRoot const elem = GM.addElement(parentElement.shadowRoot, 'iframe', {src: 'https://....'}); // appending to DOM const elem = GM.addElement(parentElement, 'a', {href: 'https://....', title: 'Some title', target: '_blank', textContent: 'Some text'});
GM.addScript (via addElement)
Utility function to inject script element (code runs in page context).
Return value: undefined
GM.addScript(text); GM_addScript(text);
// standard DOM method const script = document.createElement('script'); script.textContent = `... code ...`; document.body.appendChild(script); script.remove(); // Example 1: string const js = `function sum(x, y) { return x + y; }`; GM_addScript(js); // Example 2: function function someFunc() { // some code } GM.addScript('(' + someFunc + ')();');
GM.addStyle (via addElement)
Utility function to inject style element
Return value: undefined
GM.addStyle(text); GM_addStyle(text);
// standard DOM method const style = document.createElement('style'); style.textContent = `... css ...`; document.head.appendChild(style); // GM addStyle const css = `body { border-top: 2px solid grey; }`; GM.addStyle(css);
GM.cookie (v3.0)
List, set, and delete cookies for the current URL
details
domain:cookie.list: Astringrepresenting a domain that cookies must be associated with (they can be associated either with this exact domain or one of its subdomains).cookie.set: Astringrepresenting the domain of the cookie. If omitted, the cookie becomes a host-only cookie.
expirationDate: Anumberthat represents the expiration date of the cookie as the seconds after the UNIX epoch. It can include milliseconds in the fractional part. If omitted, the cookie becomes a session cookie.firstPartyDomain: Astringrepresenting the first-party domain with which the cookie to retrieve is associated.name: Astringrepresenting the name of the cookie.partitionKey: Anobjectdefining which storage partitions to return cookies from:- If omitted, only cookies from unpartitioned storage are returned.
- If included without
topLevelSite, all cookies from partitioned and unpartitioned storage are returned. - If included with the specified
topLevelSite, cookies from the specified partition storage are returned.
path: Astringrepresenting the path of the cookie. If omitted, this defaults to the path portion of the URL parameter.secure: Abooleanthat specifies whether the cookie is marked as secure (true), or not (false). If omitted, it defaults tofalse.session: Aboolean— filters the cookies by theirsessionproperty, allowing you to filter session or persistent cookies.value: Astringrepresenting the value of the cookie.httpOnly: Abooleanthat specifies whether the cookie is marked as HttpOnly (true), or not (false). If omitted, it defaults tofalse.-
false: These cookies can be accessed by JavaScriptdocument.cookieand CookieStore. -
true: These cookies can not be accessed by JavaScript and can only be accessed by browser API.
UserScripts can sethttpOnly: trueincookie.setbutcookie.listwill not list them as they can contain private data (e.g. login details).
-
-
storeId: It is set to the current execution context.
storeIdis removed from returned cookies for privacy reasons. -
url: It is set to current document URL, as providing access to other cookies pose a security risk
Return value: array || undefined
const list = await GM.cookie.list(); await GM.cookie.set({name: 'name', value: 'value'}); await GM.cookie.delete({name: 'name'}); GM_cookie.list(details, callback); GM_cookie.set(details, callback); GM_cookie.delete(details, callback);
GM.download
Simple file download from the Internet
urlmust be valid butfilenameis optional.url: http/https (file not allowed), can be relative to the web page- If the specified
urluses the HTTP or HTTPS protocol, then the request will include all cookies currently set for its hostname
Return value: download id number || undefined (check log for error)
// usually there is no need to wait for the following, but if needed, use await GM.download(url [, filename)]; GM_download(url [, filename]);
GM.download('https;//www.example.com/icon.jpg'); GM.download('https;//www.example.com/icon.jpg', 'new-name.jpg');
GM.fetch (breaking change in v3.0)
FireMonkey "GM fetch" API is based on the JavaScript Fetch API which provides an interface for fetching resources (including across the network). It is a more powerful and flexible replacement for XMLHttpRequest.
Check console and FireMonkey log in case of error e.g. TypeError: "NetworkError when attempting to fetch resource.".
Return value: response object || undefined
const response = await GM.fetch(url [, options]); const response = await GM_fetch(url [, options]);
- url
- http/https (file not allowed), can be relative to the web page
- options (optional)
- An options object containing any custom settings that you want to apply to the request. The possible options are:
-
method: The request method, e.g. GET, HEAD, POST, PUT, DELETE, etc. (can be omitted, defaults to 'GET')headers: Any headers you want to add to your request, contained within a Headers object or an object literal with ByteString values.body: Any body that you want to add to your request: this can be a Blob, BufferSource, FormData, URLSearchParams, or USVString object. Note that a request using the GET or HEAD method cannot have a body.mode: The mode you want to use for the request, e.g., cors, no-cors, or same-origin.credentials: The request credentials you want to use for the request:omit,same-origin(default), orinclude. To automatically send cookies for the current domain, this option must be provided. Starting with Chrome 50, this property also takes a FederatedCredential instance or a PasswordCredential instance.Since the request is made from the background script,
same-originandincludehave the same effect.cache: The cache mode you want to use for the request.redirect: The redirect mode to use: follow (automatically follow redirects), error (abort with an error if a redirect occurs), or manual (handle redirects manually). In Chrome the default is follow (before Chrome 47 it defaulted to manual).referrer: A USVString specifying no-referrer, client, or a URL. The default is client.referrerPolicy: Specifies the value of the referer HTTP header. May be one of no-referrer, no-referrer-when-downgrade, origin, origin-when-cross-origin, unsafe-url.integrity: Contains the subresource integrity value of the request (e.g. sha256-BpfBw7ivV8q2jLiT13fxDYAe2tJllusRSZ273h2nFSE=).keepalive: The keepalive option can be used to allow the request to outlive the page. Fetch with the keepalive flag is a replacement for theNavigator.sendBeacon()API.signal: An AbortSignal object instance; allows you to communicate with a fetch request and abort it if desired via an AbortController.anonymous: (Optional, true/false (default)) Iftrue, no cookie will be sent with the request.
Response
{
headers: {
age: "52512",
"cache-control": "max-age=86400, public",
"content-encoding": "br",
"content-type": "application/json",
"last-modified": "Wed, 24 Mar 2021 02:02:53 GMT",
...
},
bodyUsed: false,
ok: true,
redirected: true,
status: 200,
statusText: "OK",
type: "basic",
url: "https://developer.mozilla.org/docs/Web/API/Fetch_API/Using_Fetch",
// async functions, not in {method: 'HEAD'}
text(),
json(),
blob(),
arrayBuffer(),
formData() // has issues in MV2 userScripts
}
// check the response (if needed) if (response.ok) { ... } // HEAD request const response = await GM.fetch('https://example.com/etc', {method: 'HEAD'}); // text const response = await GM.fetch('https://example.com/etc'); const text = await response.text(); // JSON const response = await GM.fetch('https://example.com/file.json'); const obj = await response.json(); // JSON alternative const response = await GM.fetch('https://example.com/file.json'); const obj = JSON.parse(await response.text()); // with options const response = await GM.fetch('https://example.com/etc', { method: 'POST', body: JSON.stringify(data), // data can be `string` or 'object' headers:{ 'Content-Type': 'application/json' } }); const text = await response.text(); // if you don't need to wait for the response GM.fetch('https://example.com/etc') .then(response => response.json()) .then(data => callback(data)) .catch(error => console.error(error.message));
GM.getResourceText
If Metadata Block contains @resource and GM_getResourceText/GM.getResourceText, text @resource targets (not images) are fetched at the registration time and cached. Consequently, the latest version of the file will always be fetched. (v2.68)
Return value: text string || ''
const text = GM.getResourceText(resourceName); const text = GM_getResourceText(resourceName);
GM.getResourceUrl
The API works differently in comparison with other script managers as the target is not stored, but it should work out in most cases. It maps directly to the resourceURL.
Return value: URL string || undefined
const url = GM.getResourceUrl(resourceName); // note Url camel-case const url = GM_getResourceURL(resourceName); // note URL uppercase // await also works const url = await GM.getResourceUrl(resourceName);
| FireMonkey | Greasemonkey | Tampermonkey | Violentmonkey | ScriptCat | |
|---|---|---|---|---|---|
| return | actual URL | object URL | data URL | object URL (data URL If isBlobUrl is false) |
|
| store data | no | blob | data URL | data URL | |
| GM.getResourceUrl return | value | Promise | Promise | Promise |
| instead of ... | you can ... |
|---|---|
// ==UserScript== // @name GM.getResourceUrl test // @description GM.getResourceUrl() API method // @resource CSS http://www.example.com/example.css // @resource logo http://www.example.com/logo.jpg // ==/UserScript== (async () => { const link = document.createElement('link'); link.href = await GM.getResourceUrl('CSS'); link.rel = 'stylesheet'; document.body.append(style); const img = document.createElement('img'); img.src = await GM.getResourceUrl('logo'); document.body.append(img); })(); |
// ==UserScript== // @name GM.getResourceUrl test // @description GM.getResourceUrl() API method // ==/UserScript== const link = document.createElement('link'); link.href = 'http://www.example.com/example.css'; link.rel = 'stylesheet'; document.head.append(link); const img = document.createElement('img'); img.src = 'http://www.example.com/logo.jpg'; document.head.append(img); |
Example: Cannot load SVG images with GM_getResourceURL and GM_addStyle
// ==UserScript== // @name SVG test // @namespace http://tampermonkey.net/ // @version 0.1 // @description try to take over the world! // @author You // @match *://*/a.html // @resource EMOJI_SVG https://raw.githubusercontent.com/twitter/twemoji/master/assets/svg/1f36d.svg // @resource EMOJI_PNG https://raw.githubusercontent.com/twitter/twemoji/master/assets/72x72/1f36d.png // @grant GM_getResourceURL // @grant GM_addStyle // ==/UserScript== (function() { 'use strict'; const svg = GM_getResourceUrl("EMOJI_SVG"); GM_addStyle(` .icon.svg { background-image: url("${svg}"); } `); const png = GM_getResourceUrl("EMOJI_PNG"); GM_addStyle(` .icon.png { background-image: url("${png}"); } `); })();
Recommendation for above
// ==UserScript== // @name SVG test // @namespace http://tampermonkey.net/ // @version 0.1 // @description try to take over the world! // @author You // @match *://*/a.html // @grant GM_addStyle // ==/UserScript== (function() { 'use strict'; // no real need for @resource and GM_getResourceURL const svg = 'https://raw.githubusercontent.com/twitter/twemoji/master/assets/svg/1f36d.svg'; const png = 'https://raw.githubusercontent.com/twitter/twemoji/master/assets/72x72/1f36d.png'; // more efficient to combine GM_addStyle values GM_addStyle(` .icon.svg { background-image: url('${svg}'); } .icon.png { background-image: url('${png}'); } `); })();
GM.info
"GM info" is available without @grant.
A few (should-be-known) properties about the userScript itself, are removed from "GM info" in FireMonkey 3.0.
Return value: object
const info = GM.info; const info = GM_info;
The object properties in "GM info" are not unified among userscript managers.
Return Object
{
// application data
scriptHandler: 'FireMonkey',
version: 'e.g. 3.0',
platform: { // FM|VM, VM: includes browserName, browserVersion
arch: 'e.g. arm | x86-32 | x86-64',
os: 'e.g. mac | win | android | cros | linux | openbsd'
},
browser: { // FM only
buildID: 'e.g. 20260107160934'
name: 'Firefox',
vendor: 'Mozilla',
version: 'e.g. 148.0a1',
},
// script data
isIncognito: true/false, // FM 2.73 (not available in page)|TM 4.11|VM 2.15.4
injectInto: 'e.g. page', // FM|VM
scriptMetaStr: 'script metadata', // FM|GM|VM without start/end strings, TM with
script: {
name: 'script name',
version: 'script version', // FM|TM|VM: string, GM: string|null
description: 'script description',
matches: [array of match],
excludeMatches: [array of exclude-match], // FM|VM
includes: [array of regex include],
excludes: [array of regex exclude],
includeGlobs: [array of glob include], // FM only
excludeGlobs: [array of glob exclude], // FM only
grant: [array], // FM|TM|VM
require: [array], // FM|VM
resources: {object of name: url}, // GM: {...}, TM: {...}, VM: [ {...} ]
connects: [array of connect], // FM 3.0|TM
'run-at': 'e.g. document-idle', // FM|TM
runAt: 'e.g. document-idle', // FM|VM|GM, GM: runAt: "end"
namespace: '', // FM|VM: string, GM|TM: string|null
metadata: 'script metadata', // FM only, TM under info.script.header
injectInto: 'e.g. page', // FM only, VM under info.injectInto
isIncognito: true/false, // FM only
}
}
GM.log
Utility function no-longer supported by Greasemonkey, but supported by Tampermonkey and Violentmonkey (GM_log only).
Multiple parameters can be passed.
GM log in FireMonkey is an alias for console.log (v3.0). It is recommended to use console.log() instead.
Return value: undefined
GM.log(text [, text2, ...]); GM_log(text [, text2, ...]);
| instead of ... | you can ... |
|---|---|
// @grant required GM_log('one'); GM.log('one', 'two', 'three'); GM_log(GM_info); |
// no @grant required console.log('one'); console.log('one', 'two', 'three'); console.log(GM_info); |
GM.notification
Currently, only text and image are processed for notification.
options
text: text stringtitle: Not processed, script name shows as titleimage: a data URL, blob URL, or http/s URLonclick: Not processed, may be added on popular demand
Return value: notification's ID
// usually there is no need to wait for the following, but if needed, use await GM.notification('some text'); GM.notification({text: 'some text', image: 'https://example.com/icon.jpg', onclick: '...'}); GM_notification('some text'); GM_notification({text: 'some text', image: 'https://example.com/icon.jpg', onclick: '...'});
GM.openInTab
// usually there is no need to wait for the following, but if needed, use await GM.openInTab(url [, open_in_background]); GM_openInTab(url [, open_in_background]);
The default value for open_in_background honours Firefox configuration. Tabs opened with "GM openInTab" can be closed with JavaScript window.close() (v2.45) (not supported on Android ).
Return value: Boolean true/false
The object support in openInTab is not unified among userscript managers.
pinnedwould enable malware userscripts to open tabs and hide them in the pinned section. Pinned tabs are loaded automatically when browser starts.close()would enable malware userscripts to open tabs in the background (or as pinned), load malware and then close them before the user notices.onclosewould enable malware userscripts to reopen malware tabs that are closed by the user.
GM.popup (breaking change in v3.0)
FireMonkey only utility function to create a shadow DOM blank popup element with animation that can be customized to use for notification and/or configuration.
GM popup has been simplified using <dialog> element. CSS selectors :host, :backdrop, dialog and .close can be used to change its overall style.
Return value: <dialog> element
const dialog = GM.popup(); const dialog = GM_popup();
// create a new popup (multiple different popups can be created) const dialog = GM.popup(); // remove the close button (if needed) dialog.firstElementChild.remove(); // get the style element (if needed) const style = dialog.previousElementSibling; // add overall style const css = `p { background-color: #eee; }`; style.append(css); // add content as string (not recommended) const str = '<p>Good Morning</p>'; dialog.innerHTML += str; // add content as DOM const div = document.createElement('div'); const button = document.createElement('button'); // add inline style (if needed) button.setAttribute('style','color: #fff; background-color: #00f'); // add JavaScript button.addEventListener('click', someFunc); div.append(button); dialog.append(div); // show modal dialog (good for centered popup) dialog.showModal(); // show non-modal dialog (good for panels) dialog.show(); // close the dialog dialog.close(); // get the shadow host element (if needed) const host = dialog.parentNode.host; // remove the shadow host from the document host.remove(); // example with registerMenuCommand GM_registerMenuCommand('Configuration', function() { dialog.showModal(); });
Popup Panels
Built-in styles can be set when creating a popup (panel-left | panel-right | panel-top | panel-bottom)
// create a panel const popup = GM.popup(); // panel-left | panel-right | panel-top | panel-bottom dialog.className = 'panel-left'; // must show as non-modal dialog.show();
dialog
GM.registerMenuCommand
Script Commands can be accessed via toolbar pop-up and are listed under the userscript name. Please note that onclick must be a function.
Return value: undefined
GM.registerMenuCommand(name, onclick [, options]); GM_registerMenuCommand(name, onclick [, options]);
// direct method ➔ error: alert runs immediately GM.registerMenuCommand('Hello world', alert('Hello, world!'));
// anonymous function GM.registerMenuCommand('Hello world', function() { alert('Hello, world!'); }); // named function function sayHello() { alert('Hello, world!'); } GM.registerMenuCommand('Hello world', sayHello);
GM.setClipboard
Copy text (default) or data (must include type) to the clipboard
Return value: undefined or reject with error message
// usually there is no need to wait for the following, but if needed, use await GM.setClipboard(text); GM.setClipboard(data, type); GM_setClipboard(text); GM_setClipboard(data, type);
The type support in setClipboard is not unified among userscript managers.
GM.unregisterMenuCommand
Unregister the previously created Script Command.
Return value: undefined
GM.unregisterMenuCommand(name); GM_unregisterMenuCommand(name);
GM.unregisterMenuCommand('Hello, world (named)');
GM.unsafeWindow
unsafeWindow in FireMonkey is an alias for window.wrappedJSObject. You can also use window.wrappedJSObject or window.eval() to access page JavaScript globals or to create function and objects in `page` context. However, window.eval() makes the object available to the page script as well while unsafeWindow, window.wrappedJSObject do not.
Return value: window.wrappedJSObject
// page-script.js var foo = "I'm defined in a page script!"; function runTest() { console.log(foo); } // userscript.js console.log(window.foo); // undefined console.log(unsafeWindow.foo); // "I'm defined in a page script!" console.log(window.wrappedJSObject.foo); // "I'm defined in a page script!" unsafeWindow.runTest(); // "I'm defined in a page script!" window.wrappedJSObject.runTest(); // "I'm defined in a page script!" // overriding window functions const hasFocus = new window.Function('return true'); unsafeWindow.document.hasFocus = hasFocus; // another example Object.defineProperty(unsafeWindow.document, 'hidden', {value: false});
This command can open certain security holes in your user script, and it is recommended to use this command sparingly.
Please be sure to read the entire article and understand it before using it in a script.
unsafeWindow
This API object allows a User script to access "custom" properties--variable and functions defined in the page--set by the web page. The
unsafeWindowobject is shorthand forwindow.wrappedJSObject. It is the raw window object inside theXPCNativeWrapperprovided by the Greasemonkey Sandbox.USE OF UNSAFEWINDOW IS INSECURE, AND IT SHOULD BE AVOIDED WHENEVER POSSIBLE.
unsafeWindow
Safer alternatives to unsafeWindow are also listed in above page.
GM.xmlHttpRequest (breaking change in v3.0)
The xmlHttpRequest interface and mostly compatible with Greasemonkey API.
"GM.xmlHttpRequest" returns a Promise. (v3.0)
Return value: response object
The API support and the returned object are not unified among userscript managers.
| FireMonkey | Greasemonkey | Tampermonkey | Violentmonkey | ScriptCat | |
|---|---|---|---|---|---|
| GM_xmlhttpRequest | |||||
| GM.xmlhttpRequest | v2.18.3 |
// response Object const response = await GM.xmlHttpRequest({url}); // note uppercase H GM_xmlhttpRequest({url, onload}); // note lowercase h
Request
GM.xmlHttpRequest({ url, // http/https (file not allowed by browser), can be relative to the web page method, // Optional, defaults to 'GET' if omitted headers, // optional, header object to send with the request data, // optional, e.g. POST data overrideMimeType, // optional, MIME type to send with the request user, // optional, username to send with the request password, // optional, password to send with the request timeout, // optional, number of milliseconds before terminating the request (default 0 = no timeout) withCredentials, // Optional, Boolean, for cross-site Access-Control e.g. cookies, authorization headers or TLS client certificates, defaults to false, no effect on same-site requests Note: Since the request is made from the background script, true/false have the same effect. responseType // Optional, '' or 'text' (default), 'arraybuffer', 'blob', 'document', 'json' if used, get the result from response (not responseText). anonymous, // Optional, true/false (default) if true, no cookie will be sent with the request onload, // callback function onerror, // callback function onabort, // callback function ontimeout, // callback function });
Response
// onload|onerror|ontimeout|onabort, or Promise { readyState, response, responseHeaders, responseText, responseType, responseURL, responseXML, status, statusText, finalUrl, // clone of responseURL for GM|TM|VM compatibility }
// simplest (callback) GM_xmlHttpRequest({ url: 'https://example.com/', onload: response => { console.log(response.responseText); } }); // simplest (async) const response = await GM.xmlHttpRequest({ url: 'https://example.com/' }); const text = response.responseText; // POST request GM_xmlHttpRequest({ url: 'https://example.com/', method: 'POST', data: JSON.stringify(data), // data can be `string` or 'object' headers:{ 'Content-Type': 'application/json' }, onload: response => { console.log(response.responseText); }, onerror: response => { console.log(`${response.status} ${response.statusText}`); }, ); // HEAD request GM_xmlHttpRequest({ url: 'https://example.com/etc', method: 'HEAD', onload: response => { console.log(response.responseHeaders); } }); // Promisify GM_xmlhttpRequest function xhr(details) { return new Promise((resolve, reject) => { GM_xmlHttpRequest({ ...details, onload: response => resolve(response), onerror: error => reject(error), onabort: error => reject(error), ontimeout: error => reject(error), }); }); } const response = await xhr({url: 'https://example.com/'});
xmlHttpRequest withCredentials
GM.xmlHttpRequest/GM_xmlhttpRequest are sent from background script where credentials are not available. Sometimes it is necessary to send xmlHttpRequest from the page context.
xmlHttpRequest from Page Context
const xhr = new window.XMLHttpRequest(); xhr.open('GET', 'https://example.com/'); xhr.withCredentials = true; xhr.onload = response => { console.log(response.responseText); }; xhr.onerror = response => { console.log(`${response.status} ${response.statusText}`); }; xhr.send(null); // or fetch window.fetch('https://example.com/', { credentials: 'include' }) .then(response => response.json()) .catch(error => console.error(error.message));
JavaScript xmlHttpRequest and fetch
Due to a bug in Firefox userScript context, CORS fails in JavaScript new XMLHttpRequest() and fetch(). Therefore the behaviour of JavaScript xmlHttpRequest and fetch in GM|TM|VM will be somehow different. Since the introduction of MV3, work on MV2 userScripts API have stopped. UserScripts in MV3 will get the same origin as the web-page they are on. In the meantime, fetch and XMLHttpRequest are mapped to the page window as a workaround. (v2.53)
• the content script sandbox has an expanded principal that includes the extension principal, and so in manifest_version 2 extension that allows the imported fetch and XMLHttpRequest to do cross site requests based on the extension host permission (but in manifest_version 3 this is not going to be allowed anymore)
• the user script sandbox has an expanded principal but it doesn't include the extension principal and so the imported fetch and XMLHttpRequest can't do cross site requests based on the extension host permission (and this part is actually intended, the single userScript is not supposed to silently inherit expanded permission that the userScript manager extension does have)
scripts running using userScripts API seem to block all cross-origin requests
See also: Cannot send request with CORS from content scripts
Forbidden request header
After confirmation from AMO (Rob Wu 2019-09-28), modification to Cookie, Host, Origin, Referer will be allowed.
Header names are case-insensitive. (v2.30)
A forbidden request header is the name of any HTTP header that cannot be modified programmatically; specifically, an HTTP request header name (in contrast with a Forbidden response header name).
Modifying such headers is forbidden because the user agent retains full control over them. Names starting with `
Sec-` are reserved for creating new headers safe from APIs usingfetchthat grant developers control over headers, such asXMLHttpRequest.Forbidden header names start with
Proxy-orSec-, or are one of the following names:Forbidden header name
- Accept-Charset
- Accept-Encoding
- Access-Control-Request-Headers
- Access-Control-Request-Method
- Connection
- Content-Length
- Cookie
- Cookie2
- Date
- DNT
- Expect
- Host
- Keep-Alive
- Origin
- Proxy-
- Sec-
- Referer
- TE
- Trailer
- Transfer-Encoding
- Upgrade
- Via
Cookies Isolation
Browsing modes in Firefox can be divided into 3 distinct modes: normal, container, and private/incognito.
JavaScript xmlHttpRequest/fetch send cookies and credentials with their HTTP requests. In web pages, xmlHttpRequest/fetch send cookies according to the mode of the tab i.e. cookies belonging to normal browsing mode are not sent when in container or private mode, and vice versa. Cookies in each mode are isolated to preserve users' security and privacy.
Private Browsing
Private Browsing does not save your browsing information, such as history and cookies, and leaves no trace after you end the session. Firefox also has Enhanced Tracking Protection, which prevents hidden trackers from collecting your data across multiple sites and slowing down your browsing.
Private Browsing - Use Firefox without saving history
Cookies
Cookies were first used to customize websites, keep track of shopping carts, and maintain online account security, but today most are used to help companies serve targeted ads.
Here's how it works: You visit a site, an advertiser leaves a cookie on your browser. The cookie is your unique ID. Your information is stored in the cloud along with that ID. That can include which sites you visited, how long you visited them, what you clicked on, your language preferences and more.
Cookies also help advertisers deliver ads in your social media feeds. Social sites have their own tracking schemes and they're far more robust. They can track every click, post, and comment. In addition, cookies can report what you've been doing online to a social site, which is how some ads follow you into social media.
Firefox Multi-Account Containers
The Firefox Multi-Account Containers add-on isn't technically a form of private browsing or tracking protection, but it can help keep companies from knowing everything you do online. It lets you open fresh, cookie-free tabs that can be used for different accounts—personal, work, shopping, etc. That means you can use Multi-Account Containers to open several Google accounts at once without any overlap. Most trackers won't associate the different accounts, keeping your work life separate from your personal life online. Some more advanced trackers, however, can and will track you across different accounts, so beware.
Incognito browser: What it really means
"GM xmlHttpRequest" and "GM fetch" are sent from FireMonkey background script where modes do not apply. In order to honour users' browsing mode and privacy choice, FireMonkey (v2.35+) isolates cookies that are sent by the userscript according the mode of the tab userscript is running in.
- Normal Browsing Mode
- Cookies are handled by Firefox according to
withCredentialsorcredentials - Cookies set via GM API headers will merge with Firefox cookies
- Cookies sent back in the response will be handled by Firefox
- Container or Private (Incognito) Browsing Mode
- FireMonkey gets and sends contextual cookies according to the mode
- Cookies set via GM API headers will merge with above cookies
- anonymous: true
- Tells browsers to exclude credentials from the request, and ignore any credentials sent back in the response (e.g., any
Set-Cookieheader) - Cookies set via GM API headers will be sent
fetch: Same as userscript setting{credentials: 'omit'}xmlHttpRequest:mozAnonwill be set
Detecting JavaScript Navigation
When sites use JavaScript to navigate, Firefox API does not detect the navigation and does not re-inject the userScript/userCSS. In case of userCSS it usually does not matter since the rules will continue to apply nonetheless (subject to matching rules), but in case of userScript, it needs to re-run. One way to detect JavaScript navigation is to use MutationObserver with appropriate MutationObserverInit.
// select a simple node that changes in navigation to attach a MutationObserver e.g. <title> // For better performance avoid using a node with a lot of children like <body> when childList: true new MutationObserver(mutationsList => { console.log(mutationsList[0].target.textContent); // re-run the necessary function }) .observe( document.querySelector('title'), {subtree: true, childList: true} );
Navigation: navigate() method (not supported by Firefox yet)
The navigate() method of the Navigation interface navigates to a specific URL, updating any provided state in the history entries list.
See also: Implement Navigation API
Sharing objects with page scripts
In Firefox, part of the isolation between content scripts and page scripts is implemented using a feature called Xray Vision. When a script in a more-privileged scope accesses an object that's defined in a less-privileged scope it sees only the "native version" of the object.As an extension developer you should consider that scripts running in arbitrary web pages are hostile code whose aim is to steal the user's personal information, damage their computer, or attack them in some other way.
The isolation between content scripts and scripts loaded by web pages is intended to make it more difficult for hostile web pages to do this.
Since the techniques described in this section break down that isolation, they are inherently dangerous and should be used with great care.
Sharing objects with page scripts
...
Note that once you do this, you can no longer rely on any of this object's properties or functions being, or doing, what you expect. Any of them, even setters and getters, could have been redefined by untrusted code.
By default, content scripts don't get access to objects created by page scripts. However, they can communicate with page scripts using the DOM
Communicating with the web pagewindow.postMessageandwindow.addEventListenerAPIs.
In Chrome,
eval()always runs code in the context of the content script, not in the context of the page.In Firefox:
Using eval() in content scripts
- If you call
eval(), it runs code in the context of the content script.- If you call
window.eval(), it runs code in the context of the page.
cloneInto
You can use cloneinto to clone an object from the content script's context into the page script's context. cloneInto() creates a structured clone of the object in the target context, and returns a reference to the clone.
// object without methods unsafeWindow.messenger = cloneInto(obj, unsafeWindow); // object with methods unsafeWindow.messenger = cloneInto(obj, unsafeWindow, {cloneFunctions: true});
createObjectIn
You can use createObjectIn() to create a new object in the page script's context. You can then export objects and functions to that object, instead of the target scope's global window.
const foo = createObjectIn(unsafeWindow, {defineAs: 'foo'});
exportFunction
You can use exportFunction to expose a function from a content script to a page script. In this way a function defined in a content script can be called by the page script. Any non-native arguments, and the return value, are cloned into the page script's context.
unsafeWindow.setTimeout = exportFunction(setTimeout, unsafeWindow); exportFunction(notify, window, {defineAs: 'notify'});
Injecting code into page context
Sometimes it is necessary to have a script that is available to page script and/or run in page context, as an extension to the page script functions.
const script = document.createElement('script'); script.textContent = `... code ...`; document.body.appendChild(script); script.remove();
Receiving data from page context
CustomEvent can be used send data from a page script.
Example with <script>
// inject a function that generates and dispatches a CustomEvent const code = `function sendMessage(message) => { window.dispatchEvent(new CustomEvent('sendMessage', {detail: message})); }; // sendMessage when needed if(... condition ...) { sendMessage(data); }`; const script = document.createElement('script'); script.textContent = code; document.head.appendChild(script); script.remove(); // in userscript window.addEventListener('sendMessage', onMessage); function onMessage(e) { const message = e.detail; // some code }
In Firefox, window.eval() can also be used to inject code into a page context.
Example with window.eval()
// inject a function that generates & dispatches a CustomEvent const code = `function sendMessage(message) => { window.dispatchEvent(new CustomEvent('sendMessage', {detail: message})); }; // sendMessage when needed if(... condition ...) { sendMessage(data); }`; window.eval(code); // in userscript window.addEventListener('sendMessage', onMessage); function onMessage(e) { const message = e.detail; // some code }
UserCSS
Standard CSS (Cascading Style Sheets) can be injected directly into a page. If the goal is to inject CSS, it is by far more efficient to insert CSS as UserCSS, instead of using UserScript to inject CSS. Furthermore, CSS rules will apply to newly created elements in dynamically updated pages (e.g. on scroll) while JavaScript would need additional listeners to wait for new element to be created and then run then code again. FireMonkey like other CSS managers (e.g. Stylus) injects CSS at document-start by default. The benefit of document-start for CSS is that the changes will display earlier. The drawback is that the page CSS may override these CSS and if so, it is better to inject later at document-end or document-idle.
Regular Expressions in @include / @exclude are not supported in UserCSS.
Example of marking visited videos on YouTube
/* ==UserCSS== @name YouTube @match *://*.youtube.com/* ==/UserCSS== */ a[href*="/watch?v="]:visited, a[href*="/watch?v="]:visited h4, a[href*="/watch?v="]:visited yt-formatted-string { color: #f50 !important; }
Live userCSS Update
Feature was added in v2.43 to update open tabs when userCSS style changes. There are 2 types of CSS that are injected by FireMonkey:
- Permanent CSS injected by Firefox API when tab is loading
- Temporary CSS injected into tabs from Scratchpad, Popup ➔ CSS ➔ Info ➔ ▷ Run, or Live userCSS update
Live userCSS update is applied to the relevant tabs under the following circumstances:
- When a disabled userCSS is enabled, a temporary CSS is injected
- When the CSS of an enabled userCSS is edited, the temporary CSS is removed and a new one is injected
- When an enabled userCSS is disabled, the temporary CSS is removed (if present)
Please note that temporary CSS can not replace permanent CSS but can override it. The current Firefox API does not facilitate the removal of a permanent CSS.
Customise 3rd party userCSS
You can override a 3rd party userCSS with custom CSS.
- Disable the 3rd party script (it will auto-update in FM 2.19)
- Create a new userCSS
- Copy the relevant data from the target userCSS e.g.
@matchetc - Give it a new name
- Use
@requireto inject the 3rd party userCSS
3rd Party userCSS Example
/* ==UserCSS== @name ABC Style @match *://*.example.com/* @version 1.0 ==/UserCSS== */ body { border-top: 2px solid grey; }
3rd Party userCSS Custom Example
/* ==UserCSS== @name ABC Style Custom @match *://*.example.com/* @require ABC Style ==/UserCSS== */ body { border-top-color: blue; }
If the 3rd Party userCSS uses CSS custom properties (variables) e.g color: var(--main-color), you can also override them.
3rd Party userCSS Custom Example
/* ==UserCSS== @name ABC Style Custom @match *://*.example.com/* @require ABC Style ==/UserCSS== */ :root { --main-color: #fff; --border: #ddd; --color: #000; }
Character Escaping in CSS
CSS Escaping in userScript requires double escaping.
// target element <div class="RichText RichText--sans lg:mb-32"> ... </div> // in userScript const css = ` .lg\\:mb-32 { border: 1px solid red; } `; GM_addStyle(css); // in userCSS .lg\:mb-32 { border: 1px solid blue; }
UserStyle
UserStyle support has been rewritten in FireMonkey 3.0. Typical UserStyles will be converted and injected via JavaScript. Global UserStyles (without @-moz-document) will be injected as plain CSS, with User Metadata support, but without @var support.
UserStyle format, based on now-defunct userChrome.css and @-moz-document, became popular with Stylish (originally © 2005-2014 Jason Barnabe). The format concatenates multiple often-unrelated stylesheets for different sites, into one stylesheet. Since Firefox 61 (released 2018-03-13), extensions can no longer use @-moz-document which was never supported on Chrome and other browsers.
Initially in Level 3,
@document@documentwas postponed to Level 4, but then subsequently removed.
Processing UserStyle format is considerably more resource intensive than using the dedicated API to inject CSS. Extensions would have to:
- Break each UserStyle into sections for each
@-moz-document - Add listeners to monitor changes to tab/iframe URLs
- Run a comparison loop against each tab and its iframes and each
@-moz-documentin every UserStyle - If there is a match, inject the style section into the tab/iframe
Stylus (a fork of original Stylish) supports standard CSS as well as uso, less and stylus. Supporting similar features goes beyond the intended scope in FireMonkey. While partial compatibility has been provided, future developments will concentrate on FireMonkey's native userCSS format.
| API | FireMonkey (3.0) |
Stylus (2.3.18) |
Stylish (3.1.9) |
xStyle (3.1.2) |
|---|---|---|---|---|
| @var | ||||
| @advanced | ||||
|
@var type @advanced type |
text color (3/4/6/8n hex, rgb/rgba) dropdown image checkbox (n/a in CSS) range number select |
text color (3/4/6/8n hex, rgb/rgba) dropdown image checkbox range number select |
text color (6n hex, rgb) dropdown image |
text color (6n hex, rgb) dropdown image |
| @preprocessor | default (standard CSS) uso (standard CSS) less (@var only) stylus (@var only) |
default (standard CSS) uso (standard CSS) less stylus |
(uso only) | (uso only) |
| @-moz-document url | ||||
| @-moz-document domain | ||||
| @-moz-document url-prefix | ||||
| @-moz-document regexp | ||||
| Injected Scripts ✔ every page ✔ every frame ✔ when no active userstyle |
js/apply.js (10KB) | src/inject/apply.js (10KB) | scripts/inject/apply.js (6KB) | |
| Open Source | Proprietary License | minified | ||
| No Data Collection | Privacy policy | |||
| Data Collection Permissions (manifest.json) |
none | |||
| Tracking | none | favicon (icons.duckduckgo.com, can be disabled) |
||
| CSP workaround | user defined (v3.0) | settings on/off | settings on/off | |
| Firefox Users | 2k | 130k | 24k | 200 |
| Last Update | 2025 | 2025 | 2018 | 2018 |
Converting UserStyle to UserCSS
The first 3 are quite straight forward and easy to convert. The only difficulty is with the regexp(). The more complex the Regular Expressions, the more @match entries may be needed, but once done, it is easy to read and maintain.
See also: Convert to UserCSS
| From | To |
|---|---|
| @-moz-document domain('images.example.com') | @match *://images.example.com/* |
| @-moz-document url-prefix('http://www.example.com') | @match http://www.example.com/* |
| @-moz-document url('http://www.example.com/test.html') | @match http://www.example.com/test.html |
| @-moz-document regexp('http://www\.example\.(com|de)/images/.*') | @match http://www.example.com/images/* @match http://www.example.de/images/* |
| @-moz-document regexp('https?://(www\.|old\.)?reddit\.com/.*') | @match *://*.reddit.com/* |
Debugging Script and CSS
| Code linter | Check lint messages and report for errors and warnings |
| Injection Errors | Check the FireMonkey Log page for recent errors |
| JavaScript Compile Errors (Syntax Errors) | Usually appear in Browser Console Ctrl + Shift + J with a line number and a clickable link to the blob (with some string name) e.g.
SyntaxError: missing : after property id [Learn More] 7fa726ee-6f48-4a4f-a311-63b8291d98b6:18:11 |
| JavaScript Run Errors | Some JavaScript run errors in a page can be checked in Developer Tool Ctrl + Shift + I or F12 for the page that the JS is injected into e.g.
ReferenceError: abc is not defined[Learn More] 7fa726ee-6f48-4a4f-a311-63b8291d98b6:20:11 |
| View Inserted UserScript | You can see the actual blob JavaScript in Developer Tools F12 Debugger tab (you may have to refresh the page)
See also
|
| CSS Errors | The CSS error can be checked in Developer Tools F12 for the page that the CSS is injected into. You can check Style Editor tab and the injected CSS shows as a blob with a string of characters e.g. da084755-6d22-45a5-870d-82c369299d55. See also: Debugging CSS |
| Extension Developer Tools | about:debugging#/runtime/this-firefox➔ FireMonkey ➔ Inspect |
Firefox Minimum Version
| FireMonkey | Firefox | Note |
|---|---|---|
| 3.0 | 128 (2024-07-09) | Due to Firefox root certificate expiry on 2025-03-14 9 users below Firefox 128 |
| 2.68 | 93 (2021-10-05) | To benefit from ECMAScript 2022 aka ES13 2 users below Firefox 95 |
| 2.42 | 74 (2020-03-10) | To benefit from ECMAScript 2020 aka ES11 no users below Firefox 77 |
| 1.25 | 68 (2019-07-09) | Official userScripts API release |
| 1.0 - 1.24 | 65 (2019-01-29) | Initial userScripts API release API was added in Firefox 65-67 but was disabled and had to be enabled via about:config?filter=extensions.webextensions.userScripts.enabled
|
JavaScript Best Practices
- Google JavaScript Style Guide
- JavaScript Style Guide
- 30 JavaScript Best Practices for Beginners
- JavaScript best practices to improve code quality
- JavaScript Best Practices
- JavaScript best practices
- Airbnb JavaScript Style Guide
Support
Please use the GitHub Community Support.