chrome.offscreen.
Setup
Request the permission inmanifest.json:
manifest.json
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 callingcreateDocument while one exists throws. The reliable pattern is an ensure function that checks for an existing document first:
background.ts
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
Thereasons 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
pages/offscreen.html
pages/offscreen.ts
background.ts
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 implementchrome.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.

