Content scripts

Content scripts are files that run in the context of web pages. Using the standardDocumentObject Model (DOM), they are able to read details of the web pages the browser visits, makechanges to them, and pass information to their parent extension.

Understand content script capabilities

Content scripts can access the following extension APIs directly:

Content scripts are unable to access other APIs directly. But they can access them indirectly byexchanging messages with other parts of your extension.

You can also access other files in your extension from a content script, usingAPIs likefetch(). To do this, you need to declare them asweb-accessible resources. Note that this also exposes the resources to anyfirst-party or third-party scripts running on the same site.

Work in isolated worlds

Content scripts live in an isolated world, allowing a content script to make changes to itsJavaScript environment without conflicting with the page or other extensions' content scripts.

Key term: Anisolated world is a private execution environment that isn't accessible to the page or otherextensions. A practical consequence of this isolation is that JavaScript variables in an extension'scontent scripts are not visible to the host page or other extensions' content scripts. The conceptwas originally introduced with the initial launch of Chrome, providing isolation for browser tabs.

An extension may run in a web page with code similar to the following example.

webPage.html

<html>  <button>click me</button>  <script>    var greeting = "hello, ";    var button = document.getElementById("mybutton");    button.person_name = "Bob";    button.addEventListener(        "click", () => alert(greeting + button.person_name + "."), false);  </script></html>

That extension could inject the following content script using one of the techniques outlined in theInject scripts section.

content-script.js

vargreeting="hola, ";varbutton=document.getElementById("mybutton");button.person_name="Roberto";button.addEventListener("click",()=>alert(greeting+button.person_name+"."),false);

With this change, both alerts appear in sequence when the button is clicked.

Note: Not only does each extension run in its own isolated world, but content scripts and the web page dotoo. This means that none of these (web page, content scripts, and any running extensions) canaccess the context and variables of the others.

Inject scripts

Content scripts can bedeclared statically,declareddynamically, orprogrammatically injected.

Inject with static declarations

Use static content script declarations in manifest.json for scripts that should be automaticallyrun on a well known set of pages.

Statically declared scripts are registered in the manifest under the"content_scripts" key.They can include JavaScript files, CSS files, or both. All auto-run content scripts must specifymatch patterns.

manifest.json

{ "name": "My extension", ... "content_scripts": [   {     "matches": ["https://*.nytimes.com/*"],     "css": ["my-styles.css"],     "js": ["content-script.js"]   } ], ...}
NameTypeDescription
matchesarray of stringsRequired. Specifies which pages this content script will be injected into. SeeMatch Patterns for details on the syntax of these strings andMatch patterns and globs for information on how to exclude URLs.
cssarray of stringsOptional. The list of CSS files to be injected into matching pages. These are injected in the order they appear in this array, before any DOM is constructed or displayed for the page.
jsarray of stringsOptional. The list of JavaScript files to be injected into matching pages. Files are injected in the order they appear in this array. Each string in this list must contain a relative path to a resource in the extension's root directory. Leading slashes (`/`) are automatically trimmed.
run_atRunAtOptional. Specifies when the script should be injected into the page. Defaults todocument_idle.
match_about_blankbooleanOptional. Whether the script should inject into anabout:blank frame where the parent or opener frame matches one of the patterns declared inmatches. Defaults to false.
match_origin_as_fallbackbooleanOptional. Whether the script should inject in frames that were created by a matching origin, but whose URL or origin may not directly match the pattern. These include frames with different schemes, such asabout:,data:,blob:, andfilesystem:. See alsoInjecting in related frames.
worldExecutionWorldOptional. The JavaScript world for a script to execute within. Defaults toISOLATED. See alsoWork in isolated worlds.

Inject with dynamic declarations

Dynamic content scripts are useful when the match patterns for content scripts arenot well known or when content scripts shouldn't always be injected on known hosts.

Introduced in Chrome 96, dynamic declarations are similar tostaticdeclarations, but the content script object is registered with Chrome usingmethods in thechrome.scripting namespace rather than inmanifest.json. The Scripting API also allows extension developersto:

Like static declarations, dynamic declarations can include JavaScript files, CSS files, or both.

service-worker.js

chrome.scripting.registerContentScripts([{id:"session-script",js:["content.js"],persistAcrossSessions:false,matches:["*://example.com/*"],runAt:"document_start",}]).then(()=>console.log("registration complete")).catch((err)=>console.warn("unexpected error",err))

service-worker.js

chrome.scripting.updateContentScripts([{id:"session-script",excludeMatches:["*://admin.example.com/*"],}]).then(()=>console.log("registration updated"));

service-worker.js

chrome.scripting.getRegisteredContentScripts().then(scripts=>console.log("registered content scripts",scripts));

service-worker.js

chrome.scripting.unregisterContentScripts({ids:["session-script"]}).then(()=>console.log("un-registration complete"));

Inject programmatically

Use programmatic injection for content scripts that need to run in response to events or on specificoccasions.

To inject a content script programmatically, your extension needshost permissions forthe page it's trying to inject scripts into. Host permissions can either be granted byrequesting them as part of your extension's manifest or temporarily using"activeTab".

The following is a different versions of an activeTab-based extension.

manifest.json:

{  "name": "My extension",  ...  "permissions": [    "activeTab",    "scripting"  ],  "background": {    "service_worker": "background.js"  },  "action": {    "default_title": "Action Button"  }}

Content scripts can be injected as files.

content-script.js

document.body.style.backgroundColor="orange";

service-worker.js:

chrome.action.onClicked.addListener((tab)=>{chrome.scripting.executeScript({target:{tabId:tab.id},files:["content-script.js"]});});

Or, a function body can be injected and executed as a content script.

service-worker.js:

functioninjectedFunction(){document.body.style.backgroundColor="orange";}chrome.action.onClicked.addListener((tab)=>{chrome.scripting.executeScript({target:{tabId:tab.id},func:injectedFunction,});});

Be aware that the injected function is a copy of the function referenced in thechrome.scripting.executeScript() call, not the original function itself. As a result, the function'sbody must be self contained; references to variables outside of the function will cause the contentscript to throw aReferenceError.

When injecting as a function, you can also pass arguments to the function.

service-worker.js

functioninjectedFunction(color){document.body.style.backgroundColor=color;}chrome.action.onClicked.addListener((tab)=>{chrome.scripting.executeScript({target:{tabId:tab.id},func:injectedFunction,args:["orange"],});});

Exclude matches and globs

To customize specified page matching, include the following fields in a declarativeregistration.

NameTypeDescription
exclude_matchesarray of stringsOptional. Excludes pages that this content script would otherwise be injected into. SeeMatch Patterns for details of the syntax of these strings.
include_globsarray of stringsOptional. Applied aftermatches to include only those URLs that also match this glob. This is intended to emulate the@include Greasemonkey keyword.
exclude_globsarray of stringOptional. Applied aftermatches to exclude URLs that match this glob. Intended to emulate the@exclude Greasemonkey keyword.

The content script will be injected into a page if both of the following are true:

  • Its URL matches anymatches pattern and anyinclude_globs pattern.
  • The URL doesn't also match anexclude_matches orexclude_globs pattern.Because thematches property is required,exclude_matches,include_globs, andexclude_globscan only be used to limit which pages will be affected.

The following extension injects the content script intohttps://www.nytimes.com/healthbut not intohttps://www.nytimes.com/business .

manifest.json

{  "name": "My extension",  ...  "content_scripts": [    {      "matches": ["https://*.nytimes.com/*"],      "exclude_matches": ["*://*/*business*"],      "js": ["contentScript.js"]    }  ],  ...}

service-worker.js

chrome.scripting.registerContentScripts([{id:"test",matches:["https://*.nytimes.com/*"],excludeMatches:["*://*/*business*"],js:["contentScript.js"],}]);

Glob properties follow a different, more flexible syntax thanmatch patterns. Acceptable globstrings are URLs that may contain "wildcard" asterisks and question marks. The asterisk (*)matches any string of any length, including the empty string, while the question mark (?) matchesany single character.

For example, the globhttps://???.example.com/foo/\* matches any of the following:

  • https://www.example.com/foo/bar
  • https://the.example.com/foo/

However, it doesnot match the following:

  • https://my.example.com/foo/bar
  • https://example.com/foo/
  • https://www.example.com/foo

This extension injects the content script intohttps://www.nytimes.com/arts/index.html andhttps://www.nytimes.com/jobs/index.htm*, but not intohttps://www.nytimes.com/sports/index.html:

manifest.json

{  "name": "My extension",  ...  "content_scripts": [    {      "matches": ["https://*.nytimes.com/*"],      "include_globs": ["*nytimes.com/???s/*"],      "js": ["contentScript.js"]    }  ],  ...}

This extension injects the content script intohttps://history.nytimes.com andhttps://.nytimes.com/history, but not intohttps://science.nytimes.com orhttps://www.nytimes.com/science:

manifest.json

{  "name": "My extension",  ...  "content_scripts": [    {      "matches": ["https://*.nytimes.com/*"],      "exclude_globs": ["*science*"],      "js": ["contentScript.js"]    }  ],  ...}

One, all, or some of these can be included to achieve the correct scope.

manifest.json

{  "name": "My extension",  ...  "content_scripts": [    {      "matches": ["https://*.nytimes.com/*"],      "exclude_matches": ["*://*/*business*"],      "include_globs": ["*nytimes.com/???s/*"],      "exclude_globs": ["*science*"],      "js": ["contentScript.js"]    }  ],  ...}

Run time

Therun_at field controls when JavaScript files are injected into the web page. The preferred anddefault value is"document_idle". See theRunAt type for other possiblevalues.

manifest.json

{  "name": "My extension",  ...  "content_scripts": [    {      "matches": ["https://*.nytimes.com/*"],      "run_at": "document_idle",      "js": ["contentScript.js"]    }  ],  ...}

service-worker.js

chrome.scripting.registerContentScripts([{id:"test",matches:["https://*.nytimes.com/*"],runAt:"document_idle",js:["contentScript.js"],}]);
NameTypeDescription
document_idlestringPreferred. Use"document_idle" whenever possible.

The browser chooses a time to inject scripts between"document_end" and immediately after thewindow.onload event fires. The exact moment of injection depends on how complex the document is and how long it is taking to load, and is optimized for page load speed.

Content scripts running at"document_idle" don't need to listen for thewindow.onload event, they are guaranteed to run after the DOM is complete. If a script definitely needs to run afterwindow.onload, the extension can check ifonload has already fired by using thedocument.readyState property.
document_startstringScripts are injected after any files fromcss, but before any other DOM is constructed or any other script is run.
document_endstringScripts are injected immediately after the DOM is complete, but before subresources like images and frames have loaded.

Specify frames

For declarative content scripts specified in the manifest, the"all_frames" field allows the extension to specify if JavaScript and CSS files should be injected into all frames matching the specified URL requirements or only into the topmost frame in atab:

manifest.json

{  "name": "My extension",  ...  "content_scripts": [    {      "matches": ["https://*.nytimes.com/*"],      "all_frames": true,      "js": ["contentScript.js"]    }  ],  ...}

When programmatically registering content scripts usingchrome.scripting.registerContentScripts(...), theallFrames parameter can be used tospecify if the content script should be injected into all frames matching thespecified URL requirements or only into the topmost frame in a tab. This can only be used with tabId, and cannot be used if frameIds or documentIds are specified:

service-worker.js

chrome.scripting.registerContentScripts([{id:"test",matches:["https://*.nytimes.com/*"],allFrames:true,js:["contentScript.js"],}]);

Inject in to related frames

Extensions may want to run scripts in frames that are related to a matchingframe, but don't themselves match. A common scenario when this is the case isfor frames with URLs that were created by a matching frame, but whose URLs don'tthemselves match the script's specified patterns.

This is the case when an extension wants to inject in frames with URLs thathaveabout:,data:,blob:, andfilesystem: schemes. In these cases, theURL won't match the content script's pattern (and, in the case ofabout: anddata:, don't even include the parent URL or origin in the URLat all, as inabout:blank ordata:text/html,<html>Hello, World!</html>).However, these frames can still be associated with the creating frame.

To inject into these frames, extensions can specify the"match_origin_as_fallback" property on a content script specification in themanifest.

manifest.json

{"name":"My extension",..."content_scripts":[{"matches":["https://*.google.com/*"],"match_origin_as_fallback":true,"js":["contentScript.js"]}],...}

When specified and set totrue, Chrome will look at the origin of theinitiator of the frame to determine whether the frame matches, rather than atthe URL of the frame itself. Note that this might also be different than thetarget frame'sorigin (e.g.,data: URLs have a null origin).

The initiator of the frame is the frame that created or navigated the targetframe. While this is commonly the direct parent or opener, it may not be (as inthe case of a frame navigating an iframe within an iframe).

Because this compares theorigin of the initiator frame, the initiator framecould be on at any path from that origin. To make this implication clear, Chromerequires any content scripts specified with"match_origin_as_fallback"set totrue to also specify a path of*.

When both"match_origin_as_fallback" and"match_about_blank" are specified,"match_origin_as_fallback" takes priority.

Communication with the embedding page

Although the execution environments of content scripts and the pages that host them are isolatedfrom each other, they share access to the page's DOM. If the page wishes to communicate with thecontent script, or with the extension through the content script, it must do so through the shared DOM.

An example can be accomplished usingwindow.postMessage():

content-script.js

varport=chrome.runtime.connect();window.addEventListener("message",(event)=>{// We only accept messages from ourselvesif(event.source!==window){return;}if(event.data.type &&(event.data.type==="FROM_PAGE")){console.log("Content script received: "+event.data.text);port.postMessage(event.data.text);}},false);

example.js

document.getElementById("theButton").addEventListener("click",()=>{window.postMessage({type:"FROM_PAGE",text:"Hello from the webpage!"},"*");},false);

The non-extension page, example.html, posts messages to itself. This message is intercepted andinspected by the content script and then posted to the extension process. In this way, the pageestablishes a line of communication to the extension process. The reverse is possible throughsimilar means.

Access extension files

To access an extension file from a content script, you can callchrome.runtime.getURL() to get theabsolute URL of your extension asset as shown in the following example (content.js):

content-script.js

letimage=chrome.runtime.getURL("images/my_image.png")

To use fonts or images in a CSS file, you can use@@extension_id to construct a URL as shown in the following example (content.css):

content.css

body{background-image:url('chrome-extension://__MSG_@@extension_id__/background.png');}@font-face{font-family:'Stint Ultra Expanded';font-style:normal;font-weight:400;src:url('chrome-extension://__MSG_@@extension_id__/fonts/Stint Ultra Expanded.woff')format('woff');}

All assets must be declared asweb accessible resources in themanifest.json file:

manifest.json

{..."web_accessible_resources":[{"resources":["images/*.png"],"matches":["https://example.com/*"]},{"resources":["fonts/*.woff"],"matches":["https://example.com/*"]}],...}

Stay secure

While isolated worlds provide a layer of protection, using content scripts can createvulnerabilities in an extension and the web page. If the content script receives content from aseparate website, such as by callingfetch(), be careful to filter content againstcross-site scripting attacks before injecting it. Only communicate over HTTPS in order toavoid"man-in-the-middle" attacks.

Be sure to filter for malicious web pages. For example, the following patterns are dangerous, anddisallowed in Manifest V3:

Don't

content-script.js

constdata=document.getElementById("json-data");// WARNING! Might be evaluating an evil script!constparsed=eval("("+data+")");
Don't

content-script.js

constelmt_id=...// WARNING! elmt_id might be '); ... evil script ... //'!window.setTimeout("animate("+elmt_id+")",200);

Instead, prefer safer APIs that don't run scripts:

Do

content-script.js

constdata=document.getElementById("json-data")// JSON.parse does not evaluate the attacker's scripts.constparsed=JSON.parse(data);
Do

content-script.js

constelmt_id=...// The closure form of setTimeout does not evaluate scripts.window.setTimeout(()=>animate(elmt_id),200);

Except as otherwise noted, the content of this page is licensed under theCreative Commons Attribution 4.0 License, and code samples are licensed under theApache 2.0 License. For details, see theGoogle Developers Site Policies. Java is a registered trademark of Oracle and/or its affiliates.

Last updated 2012-09-17 UTC.