Content Scripts
CRXJS provides Vite HMR for isolated and main world content scripts since v2.7.0.
Since @crxjs/vite-plugin v2.7.0, content script HMR works in both Chrome's isolated world and main world.
For React projects, use @vitejs/plugin-react. HMR with @vitejs/plugin-react-swc is not covered by CRXJS's integration tests and has a known unresolved report. If you choose the SWC plugin, verify both extension-page and content-script HMR in your project.
Execution model
A content script is extension JavaScript that Chrome runs on designated web pages. The page where it runs is called the host page.
By default, Chrome runs content scripts in an isolated world. The content script
and host page share the same DOM, but their JavaScript global scopes and
variables are separate. A content script configured with world: 'MAIN' runs in
the host page's JavaScript environment instead. See Chrome's
content script documentation.
Declare a content script
Declare static content scripts under content_scripts in the manifest. The
required matches patterns determine which host pages receive the script.
CSS isolation
JavaScript isolation does not isolate CSS. Styles imported by a content script participate in the host document's cascade: host-page rules can affect the UI created by your content script, and broad content-script selectors can affect the host page. Use narrowly scoped selectors or mount complex UI inside a Shadow DOM when you need a style boundary.
Static Assets
Feel free to import static assets! CRXJS automatically declares imported content
script dependencies as web_accessible_resources in the manifest.
Use the extension URL
Content scripts share the origin of the host page, so convert imported static assets to the extension origin using the Chrome API.
IIFE content scripts
CRXJS normally runs content scripts through an async loader. That loader enables
Vite HMR and regular ESM imports, but it can be too late for document_start
scripts that must patch host page APIs before page code runs.
Use an IIFE content script for timing-sensitive work like intercepting fetch,
XMLHttpRequest, DOM events, or globals in the main world. CRXJS emits the
script as a standalone bundle with its imports inlined, and Chrome runs that
file directly.
IIFE content scripts do not receive in-place Vite HMR. During development CRXJS rebuilds the standalone file, but the updated script only runs after the host page reloads or navigates again.
Recommended architecture
Keep most of your extension logic in the default isolated world content script. It stays separated from page JavaScript, can use the extension APIs available to content scripts, and receives full Vite HMR in development.
Use a MAIN world IIFE only for the part that must run inside the host page
environment. Treat it as a small bridge: install the early hook at
document_start, then pass commands or data to the isolated content script with
window.postMessage, CustomEvent, or DOM events. This keeps the non-HMR script
small and stable.
Enable IIFE output
Add .iife before the source extension. This works for
.iife.ts, .iife.tsx, .iife.js, and .iife.jsx.
HTML in content scripts
It is possible to inject an extension page into a host page using an iframe. The
host page CSP does not affect the injected iframe even if the host page
specifies the frame-src policy.
An injected extension page loads inside a cross-origin iframe, so it does not have access to the host page DOM like a content script.
Injected extension pages do have access to the full Chrome API, however.
If you load an HTML file from a content script, you need to declare the file as a web-accessible resource.
You will also need to add the HTML file to your Vite config under
build.rollupOptions.input.
Imported HTML
If you need to render complex HTML in a content script without a framework, an
HTML file can serve as a static fragment by importing it as text using the
?raw query. This technique does not require the file to be web-accessible, and
you don't need to declare it in the Vite config.
Importing an HTML file as text lets you take advantage of IDE language services
for HTML files. Depending on your HTML, this technique may be more concise than
using document.createElement().