Integrated Browser Extensions

Userscripts, custom CSS and request blocking for the VS Code Integrated Browser. Tampermonkey, Stylus and a sliver of uBlock, minus the part where you leave VS Code, alt-tab to your browser, and resurface 40 minutes later wondering what you came in for.
What this is
VS Code's Integrated Browser renders pages in a real Chromium WebContentsView and exposes a Chrome DevTools Protocol session to extensions. This extension uses that to inject userscripts at document-start, give them a GM_* API, apply per-site CSS, and block or rewrite requests.
It is a content-script engine, not a Chrome extension host: there is no support for .crx / Web Store extensions, chrome.runtime / chrome.tabs, or background service workers.
Install
Open the Extensions view in VS Code (Ctrl/Cmd+Shift+X), search for Integrated Browser Extensions, and click Install - or get it from the Marketplace.
You can also install the .vsix directly:
code --install-extension integrated-browser-extensions.vsix
[!IMPORTANT]
This extension uses the browser extension API. In VS Code builds where that API is not enabled, the panel and rule editing still work, but nothing is injected into pages. To enable it, launch with:
code --enable-proposed-api ryan.integrated-browser-extensions
The panel shows a banner when this is the case.
After installing, open the Browser Scripts view from the Activity Bar.
Quick start
- Open a page in the Integrated Browser.
- Open the Browser Scripts view in the Activity Bar.
- Click New Userscript (the
+ button) - a *.user.js file opens in the editor.
- Edit and save. The script is applied to matching tabs immediately; reload the page if it uses
@run-at document-start.
To bring in an existing userscript, drop a *.user.js file into <globalStorage>/userscripts/ (or point integratedBrowserExtensions.extensionDirectory at a folder of them - see below).
Writing a userscript
New scripts start with a metadata block. Tampermonkey / Greasemonkey / Violentmonkey syntax is supported:
// ==UserScript==
// @name Hide the cookie banner
// @namespace me
// @version 1.0.0
// @description Remove the cookie consent overlay on example.com
// @match https://*.example.com/*
// @exclude https://example.com/checkout/*
// @run-at document-start
// @grant GM_addStyle
// @require https://cdn.jsdelivr.net/npm/some-lib@1/dist/some-lib.min.js
// @resource logo https://example.com/logo.svg
// @connect api.example.com
// ==/UserScript==
(function () {
'use strict';
GM_addStyle(`#cookie-overlay, .cmp-banner { display: none !important; }`);
GM_xmlhttpRequest({
method: 'GET',
url: 'https://api.example.com/dismiss',
onload: (res) => console.log('dismissed', res.status)
});
})();
Metadata read: @name @namespace @version @description @author @homepage/@homepageURL @icon @match @include @exclude @grant @require @resource @connect @run-at (document-start / document-end / document-idle) @inject-into @noframes.
GM_* / GM.* provided: GM_info, GM_addStyle, GM_addElement, GM_getValue / GM_setValue / GM_deleteValue / GM_listValues, GM_getResourceText / GM_getResourceURL, GM_xmlhttpRequest / GM.xmlHttpRequest, GM_setClipboard, GM_notification, GM_openInTab, GM_download, GM_log, plus unsafeWindow. Promise-returning GM.* equivalents are also available. (GM_registerMenuCommand / GM_unregisterMenuCommand are accepted as no-ops so scripts that use them still load.)
GM_xmlhttpRequest runs in the extension host, so it bypasses page CORS. It is limited to hosts allowed by the script's @connect directives and the integratedBrowserExtensions.gmXhr.allowedHosts setting.
Using your existing scripts
Point integratedBrowserExtensions.extensionDirectory at directories or globs containing *.user.js files (the settings UI provides an Add Item button):
"integratedBrowserExtensions.extensionDirectory": [
"${userHome}/Documents/userscripts",
"/srv/shared/scripts/**/*.user.js"
]
These appear as read-only external scripts. You can enable and disable them; editing happens in their source files.
Live editing
Userscripts are plain files - edit and save them. On save, the watcher re-applies the script to matching tabs. By default the new version runs on the next navigation; enable integratedBrowserExtensions.reloadOnSave to reload tabs whose URL matches the changed script so it re-runs cleanly. CSS rules hot-swap in place without a reload regardless of this setting.
Custom CSS
CSS rules work like userscripts: each is a *.user.css file with a metadata header. Click + in the CSS Rules section to create one and it opens in the editor. The header uses the same @match / @include / @exclude matching as userscripts:
/* ==UserStyle==
@name Quieter GitHub
@match https://github.com/*
==/UserStyle== */
.js-notification-shelf, .flash-warn { display: none !important; }
Save the file and the CSS is injected at document-start on matching pages. Edits hot-swap into the page without a reload, and disabling or deleting a rule removes it from the page.
Network rules
In the Network Rules section, click + and expand the rule:
- Action - Block, Redirect, or Modify headers
- URL pattern - a
* glob or /regex/, matched against the full request URL
- Resource types (optional) -
Document, Script, Image, XHR, Fetch, Stylesheet, Media, ...
- Redirect to (redirect) - destination URL;
$1, $2, ... apply when the pattern is a regex
- Request / Response headers (modify headers) - one per line:
Header: value to set, -Header to remove
Rules are enforced through the CDP Fetch domain on every browser tab.
Settings
| Setting |
Default |
Description |
integratedBrowserExtensions.enabled |
true |
Master switch for all injection |
integratedBrowserExtensions.userscripts.enabled |
true |
Apply userscripts |
integratedBrowserExtensions.styles.enabled |
true |
Apply custom CSS |
integratedBrowserExtensions.network.enabled |
true |
Apply network rules |
integratedBrowserExtensions.injectIntoSubframes |
true |
Inject into cross-origin iframes too |
integratedBrowserExtensions.reloadOnSave |
false |
When a userscript changes, reload browser tabs whose URL matches it (CSS hot-swaps without a reload regardless) |
integratedBrowserExtensions.extensionDirectory |
[] |
Extra directories / globs scanned for *.user.js files (supports * and **; ${userHome} / ~ are expanded) |
integratedBrowserExtensions.gmXhr.allowedHosts |
["*"] |
Host patterns GM_xmlhttpRequest may reach |
integratedBrowserExtensions.debugMode |
false |
Emit console.info lines in the page when a userscript or CSS rule is injected, and log entries when a network rule is enabled on a tab or matches a request |
integratedBrowserExtensions.debugCdp |
false |
Log every CDP message (verbose) |
Commands
| Command |
Description |
Browser Scripts: New Userscript |
Create a userscript and open it for editing |
Browser Scripts: Re-apply To All Browser Tabs |
Rebuild and re-inject into every open browser tab |
Browser Scripts: Show Log |
Open the extension's output channel |
Limitations
- Not a Chromium extension host: no
.crx / Web Store extensions, chrome.runtime / chrome.tabs, background service workers, or popup/options pages.
- Userscripts run in the page's main world, so
@inject-into content behaves like page and unsafeWindow === window.
GM_getValue / GM_setValue are synchronous against a snapshot taken when the script was injected; writes from another tab become visible after the next navigation or re-apply. Use the async GM.getValue / GM.setValue if you need cross-tab freshness.
- In some VS Code builds the
browser API is a proposed API - see the note under Install.
Support
If this is useful to you and you'd like to support its development, you can buy me a coffee on Ko-fi - always optional, always appreciated.

License
MIT