Skip to main content
Manifest V3 service workers have no DOM. When background logic needs DOM parsing, audio playback, clipboard access, or another window-only API, Chrome’s answer is an offscreen document: an invisible extension page you create on demand with chrome.offscreen.

Setup

Request the permission in manifest.json:
manifest.json
The offscreen page is not a manifest entrypoint, so declare it through the pages/ special folder and Extension.js compiles it like any other HTML entry:
pages/offscreen.html

Create, reuse, close

Chrome allows one offscreen document per extension, and calling createDocument while one exists throws. The reliable pattern is an ensure function that checks for an existing document first:
background.ts
The creating lock matters: two events can race into ensureOffscreen before the document exists, and the second createDocument call would throw. Close it when the work is done to free memory:

Choosing a reason

The reasons array tells Chrome why the document exists. Common values: One behavior worth knowing: with AUDIO_PLAYBACK, Chrome closes the document automatically about 30 seconds after audio stops playing. For other reasons the document lives until you close it or the extension unloads.

Clipboard access from the background

Writing to the clipboard is the most common reason to reach for an offscreen document. navigator.clipboard lives on window, and a manifest v3 service worker has no window, so the write has to happen on a real page. Ask for the permission and the reason:
manifest.json
Build the offscreen page with a textarea that the copy command can use:
pages/offscreen.html
pages/offscreen.ts
Then send the text from the service worker:
background.ts
Pass chrome.offscreen.Reason.CLIPBOARD to createDocument for this case. document.execCommand("copy") is the call that works here. The newer navigator.clipboard.writeText needs a focused document, and an offscreen document is never focused. Reading the clipboard needs the clipboardRead permission and the same document. A content script or an extension page that the user opened does not need any of this. Both have a real window, so navigator.clipboard.writeText works after a user gesture.

Extension.js ships the page for you

chrome.offscreen.createDocument takes a URL string, not an import. Extension.js reads that string during the build and adds the HTML page, plus everything that the page loads, to the output. A page that only the service worker names still reaches dist/. The string has to be a plain literal for that to work. A URL that you build at runtime is invisible to the build, and the page is left out.

Talking to the document

Offscreen documents use standard runtime messaging. Scope your messages so other surfaces ignore them:
pages/offscreen.ts

Firefox

Firefox does not implement chrome.offscreen. Its Manifest V3 background runs as an event page that already has DOM access, so the same DOM work runs directly in the background script there. Use browser-specific manifest fields and a capability check (typeof chrome.offscreen !== "undefined") to branch.

See also