🌐

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 1GM_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
range
number
The units value - framed in (double) quotes ...
⚠️ because we're parsing the meta data as JSON, all fractional values must include an integer value, e.g. [.5] will cause a parse error, so use [0.5]
select
Use a JSON format for the values set in the select's options.
Writing UserCSS
@var checkboxRequires JavaScript to process logical true/false which is not supported by Firefox yet
See also: if()
@preprocessor lessRequires Less JavaScript Library
@preprocessor stylusStylus 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 Metadata
See also: defaults
No @run-at and script runs late Try setting @run-at document-end in User Metadata
See also: defaults
window.addEventListener does not fire
'load/DOMContentLoaded'
Try setting @run-at document-end in User Metadata
See 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 relaxation
See: CSP Exclude
Trusted Types API and jQuery 1-3
(Firefox 148)
TypeError: Element.innerHTML setter: Sink type mismatch violation blocked by CSP
Try 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

The XMLHttpRequest() API is supported in extensions outside of the service worker

Fetch() vs. XMLHttpRequest()

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 content context which has more privilege than userScript context 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 content context which has more privilege than userScript context 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 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 Themes and select Extensions, or go to: about:addons
Click the Tools for all add-ons cogwheel
Click Manage Extension Shortcuts in 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.

Storage quotas for sync data
Maximum total size 102,400 bytes (100 KB)
Maximum item size 8,192 bytes (8 KB)
Maximum number of items 512

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

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.

Editor Comparison
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 @uploadURL value (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/@exclude to @match/@exclude-match wherever 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
Keyboard Commands (when editor is active)
Description Keyboard Command
Save (FireMonkey)Ctrl + S
Toggle fullscreen (FireMonkey)F11
Exit fullscreen (FireMonkey)Esc
Toggle word wrap (FireMonkey)Alt + Z
Command PaletteF1
CutCtrl + X
CopyCtrl + C
PasteCtrl + V
UndoCtrl + Z
RedoCtrl + Y
SearchCtrl + F
ReplaceCtrl + H
Show hintCtrl + Space
Format DocumentShift + Alt + F
Toggle line commentCtrl + /
Toggle block CommentCtrl + Shift + A
Increase indentTab or Ctrl + ]
Decrease indentShift + Tab or Ctrl + [
Move a line downAlt + Down
Move a line upAlt + Up
Select allCtrl + A
Select downwardShift + Down
Select rightShift + Right
Select leftShift + Left
Select upwardShift + 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.

Script Template Variable Comparison
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.

meta.js/meta.css

FireMonkey only checks @version, therefore a minimal meta.js/meta.css would suffice.

@version            1.0
Update Comparison
FireMonkey Greasemonkey Tampermonkey Violentmonkey ScriptCat
@updateURL meta or download target meta target meta target meta target
@downloadURL meta or download target download target download target download target
@metaURL v2.68
install source
last-modified header
GreasyFork/SleazyFork GitHub/Gist OpenUserJS
@updateURL
@downloadURL
meta.js
meta.css
last-modified header

Metadata Block

FireMonkey supports the following entries based on the Firefox API.

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
Metadata Comparison
Metadata FireMonkey Greasemonkey Tampermonkey Violentmonkey ScriptCat
@allFrames
@connect v3.0
@container (via @run-in)
@downloadURL
@exclude-match
@exclude
@grant
@group v3.0
@include
@inject-into v2.13(via @sandbox)
@match v0.9.8 (2011)
@matchAboutBlank
@metaURL v2.68
@name:xx-YY v4.11
@name
@noframes
@origin v3.0
@require
@resource
@run-at
@top-level-await built-in v3.0built-inv2.19.2 built-in
@unwrap v3.0v2.13.1
@updateURL (undocumented)
@uploadURL v3.0
@var
@version (mandatory)
@antifeature (popup info only)
@author (popup info only)(undocumented)
@description:xx-YY (popup info only)v4.11
@description (popup info only)
@homepage (popup info only)
@homepageURL (popup info only)
@source (popup info only)
@support (popup info only)
@supportURL (popup info only)
@website (popup info only)
@namespace
@run-in (via @container)v5.3.0
@sandbox (via @inject-into)v4.18(via @inject-into)
@webRequest (not on MV3 Chrome)
@icon 1
@icon64, @icon64URL
@iconURL, @defaulticon
@runAt (deprecated v3.0)
@matches (deprecated v3.0)
@excludeMatches (deprecated v3.0)
@includeGlob (deprecated v3.0)
@excludeGlob (deprecated v3.0)
@includeGlobs (deprecated v3.0)
@excludeGlobs (deprecated v3.0)

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.

Defaults Comparison
FireMonkey Greasemonkey Tampermonkey Violentmonkey ScriptCat Stylus
@run-atdocument-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:

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 matches is 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:

  • match the matches property
  • AND match the include_globs property, if present
  • AND NOT match the exclude_matches property, if present
  • AND NOT match the exclude_globs property, if present
content_scripts

Having mixed @match/@exclude-match with @include/@exclude may have unexpected results.

Browser URL matching logic
@match works as expected
@include works as expected
@match + @include (MV2) @match will be ignored and only @include works
@match + @include (MV3) should work as expected
@include with regular expression
(not supported by browser API)
results in injection into every page and processing afterwards
Examples of converting Glob Patterns to Match Patterns
@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:

  1. match and exclude-match (match patterns) (userScripts/userCSS/userStyle)
  2. include and exclude (glob patterns) (userScripts/userCSS/userStyle)
  3. exclude with regular expression (userScripts)
  4. 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

// @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.

@container          default
@container          private
@container          container-1

@grant (breaking change in v3.0)

🛡️ 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.

// ==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 @match metadata imperative is very similar to @include, however it is safer. It sets more strict rules on what the * character means.

Greasemonkey @match

It is recommended to use @match / @exclude-match rather than @include / @exclude because the match rules are safer and more strict.

Violentmonkey Matching

Regular Expression in @include / @exclude

Regular Expression support has been implemented for @include / @exclude (v2.5).

Statistics show that only a small percentage of userscripts use regular expression in @include and @exclude.

total use regex percentage
scripts/styles113,1512,9732.6%
install count211,144,3646,556,3943.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

@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.orgNo 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. If true, the code will be injected into embedded about:blank and about:srcdoc frames if your extension has access to their parent document. The code cannot be inserted in top-level about: frames.

Defaults to false.

tabs.executeScript()

@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.

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 @inject-into page, may fail since DOM might not be available yet.

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]
  • Array must be a valid JSON e.g. .5, or single quotes 'em' are invalid
  • Array must contain 4 or 5 items
  • Use null when not setting a value
  • If unit is not included, value will be a plain number
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
  • Valid JSON array of values e.g. ["value1", "value2", "value3"]
  • Valid JSON object e.g. {"label1": "value1", "label2": "value2"}
  • If the string requires quotes, it must be included in the value e.g. font names with spaces "\"Times New Roman\""
/* 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.

User Script API Comparison
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.getValuesv2.68, v3.0v5.3.0 v2.19.1
GM.setValue727
GM.setValuesv2.68, v3.0v5.3.0 v2.19.1
GM.deleteValue208
GM.deleteValuesv2.68, v3.0v5.3.0 v2.19.1
GM.listValues77
GM.addValueChangeListener(undocumented)10
GM.removeValueChangeListener(undocumented)1
DOM
GM.addElementv4.12v2.13.14
GM.addScript0
GM.addStyle102
GM.popup0
Connect
GM.downloadv2.18.318
GM.fetch0
GM.xmlHttpRequestv3.0v2.18.3699
Other
GM.getResourceText(undocumented)11
GM.getResourceUrl(different) v2.13.130
GM.info69
GM.log(undocumented)(not working)7
GM.notification42
GM.openInTab178
GM.setClipboard 77
GM.registerMenuCommandv4.11 v2.12.10131
GM.unregisterMenuCommand6
GM.cookiev3.0(not working)9
GM.getTab 2(not working)3
GM.getTabs 2(not working)0
GM.saveTab 2
GM.audio
Storage
GM_getValue5,837
GM_getValuesv2.68, v3.0v5.3.0 v2.19.1
GM_setValue3,782
GM_setValuesv2.68, v3.0v5.3.0 v2.19.1
GM_deleteValue1,577
GM_deleteValuesv2.68, v3.0v5.3.0 v2.19.1
GM_listValues711
GM_addValueChangeListener(mapped to GM.*)209
GM_removeValueChangeListener(mapped to GM.*)77
DOM
GM_addElement(mapped to GM.*)v4.11 v2.13.141
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_xmlhttpRequest5,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 243
GM_getTabs 239
GM_saveTab 237
GM_audio
unsafeWindow userScript
page
content content
page
content
page
content 3,057
window.close 3limited supportv2.6.2 279
window.focus 4v2.12.10 135
window.onurlchangev4.11 69
window.external 5v2.74v4.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.

MV2 Context Layers
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)
MV3 Context Layers: ExecutionWorld
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 MAIN world unless it is acceptable for web pages to read, access, or modify the logic or data that flows through the executed code.

Userscripts injected in content context, carry increased risks.

MV2 Injection Context Comparison
FireMonkey Greasemonkey Tampermonkey Violentmonkey ScriptCat
default userScript content page page page
@inject-into page (via @sandbox) page | content content
@grant    *** userScript content page no change page
@grant    none
(no @grant)
page (v3.0) content page no change page
@unwrap
(no GM API)
page page page
unsafeWindow userScript
page
content page content
page
page
GM info
(without @grant)
userScript
page
content page content
page
page
(no GM.info)
GM functions userScript
page
content page content
page
page
isolated context 1
CSP workaround 2 user defined (v3.0) auto (changeable) (removes all)
Context Security
browser(chrome)
browser(chrome).storage
ReferenceError: browser(chrome) is not defined Object { menus: Getter & Setter, manifest: {…} }
Object { onChanged: Getter & Setter, local: {…} }
ReferenceError: browser(chrome) is not defined ReferenceError: browser(chrome) is not defined ReferenceError: browser(chrome) is not defined

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.

2 Content Security Policy

MV3 Injection Context Comparison
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:

Wrapper Comparison
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.

Dynamic import() Issues
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”).
Alternative import()
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

Storage Comparison
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
Storage Types
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.

// 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
  • <head> for 'link', 'meta' tags
  • <head> | <body> for 'script', 'style' tags
  • <body> | <html> for others
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

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);
cookie Option Comparison
FireMonkey Greasemonkey Tampermonkey Violentmonkey ScriptCat
list
return: array
domain (optional)
firstPartyDomain (optional)
name (optional)
partitionKey (optional)
path (optional)
secure (optional)
session (optional)
domain
name
partitionKey
path
url
domain
name
partitionKey
path
url
set
return: object | null
domain (optional)
expirationDate (optional)
firstPartyDomain (optional)
httpOnly (optional)
name
partitionKey (optional)
path (optional)
secure (optional)
value
domain
expirationDate
firstPartyDomain
httpOnly
name
partitionKey
path
secure
url
value
domain
expirationDate
httpOnly
name
partitionKey
path
secure
url
value
delete
return: object | null
firstPartyDomain (optional)
name
partitionKey (optional)
firstPartyDomain
name
partitionKey
url
name
partitionKey
url

GM.download

Simple file download from the Internet

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), or include. 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-origin and include have 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 the Navigator.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)) If true, 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);
getResourceUrl Comparison
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
getResourceUrl Alternative
instead of ... you can ...

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

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 ...

GM.notification

Currently, only text and image are processed for notification.

options

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.

openInTab Option Comparison
FireMonkey Greasemonkey Tampermonkey Violentmonkey ScriptCat
open_in_background
(default value)
false false true false false
object
(default values)
active true (v2.48) insert Firefox default (true) setParent same as opener tab incognito same as opener tab pinned same as opener tab container same as opener tab active false insert true setParent false incognito false active true insert true (v2.11.0) pinned false (v2.12.5) container 0, 1, etc (v2.12.5) active false insert true ? ? ?
return value true/false (v2.48) close function closed boolean onclose callback close function closed boolean onclose callback close function

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();

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]);
registerMenuCommand Option Comparison
FireMonkey Greasemonkey Tampermonkey Violentmonkey ScriptCat
options id autoClose title accessKey id autoClose title icon
return value undefined id name (v2.12.5) or id (v2.15.9)

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.

setClipboard Comparison
FireMonkey Greasemonkey Tampermonkey Violentmonkey ScriptCat
text
type v2.45
object e.g. {mimetype: 'text/plain'}
string MIME type e.g. 'text/plain'

object e.g. {type: 'text', mimetype: 'text/plain'}
string e.g. 'text' or 'html'

string MIME type e.g. 'text/plain'

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

MV2 unsafeWindow Comparison
Context FireMonkey Greasemonkey Tampermonkey Violentmonkey ScriptCat
content window.wrappedJSObject
access page DOM
access page JS
global VM object
access page DOM
access page JS
userScript window.wrappedJSObject
access page DOM
access page JS
page window
access page DOM
access page JS
window
access page DOM
access page JS
window
access page DOM
access page JS
window
access page DOM
access page JS
// 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 unsafeWindow object is shorthand for window.wrappedJSObject. It is the raw window object inside the XPCNativeWrapper provided 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 using fetch that grant developers control over headers, such as XMLHttpRequest.

Forbidden header names start with Proxy- or Sec-, or are one of the following names:

  • 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
Forbidden header name

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 withCredentials or credentials
  • xmlHttpRequest: Firefox sends cookies, withCredentials has no effect
  • fetch : Firefox sends cookies, unless {credentials: 'omit'}
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-Cookie header)
Cookies set via GM API headers will be sent
fetch: Same as userscript setting {credentials: 'omit'}
xmlHttpRequest: mozAnon will be set
Cookies Isolation Comparison
FireMonkey Greasemonkey Tampermonkey Violentmonkey ScriptCat
Sending Cookies
GM xmlHttpRequest v4.12.6132
GM xmlHttpRequest
withCredentials
(not effective)
GM fetch
GM fetch
credentials
(only 'omit' effective)
anonymous flag v2.10.1
anonymous
block Set-Cookie
v2.12.5
Container/Incognito
block Set-Cookie
GM download
First-party isolation

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.
...
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.

Sharing objects with page scripts

By default, content scripts don't get access to objects created by page scripts. However, they can communicate with page scripts using the DOM window.postMessage and window.addEventListener APIs.

Communicating with the web page

In Chrome, eval() always runs code in the context of the content script, not in the context of the page.

In Firefox:

  • 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.
Using eval() in content scripts

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:

Live userCSS update is applied to the relevant tabs under the following circumstances:

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.

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 was postponed to Level 4, but then subsequently removed.

@document

Processing UserStyle format is considerably more resource intensive than using the dedicated API to inject CSS. Extensions would have to:

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.

UserStyle API Comparison
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)
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

Support

Please use the GitHub Community Support.