> ## Documentation Index
> Fetch the complete documentation index at: https://docs.extensy.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# Content Scripts

> Inject JavaScript into web pages to read and modify the DOM using Extensy-generated content scripts.

Content scripts are JavaScript files your extension injects into web pages. They run in the context of the page — they can read and modify the DOM, listen for user interactions, and observe page content — but they are isolated from the page's own JavaScript.

## How content scripts work

When the browser navigates to a URL that matches your declared patterns, Chrome automatically injects your content script into the page. The script can then interact with the DOM like any other script on the page, but it cannot directly access JavaScript variables defined by the page itself.

> [!NOTE]
>
> Content scripts have access to a limited subset of `chrome.*` APIs. For anything outside that subset — like `chrome.tabs` or `chrome.alarms` — use message passing to communicate with the background service worker.

## Declare a content script in the manifest

Register content scripts under `"content_scripts"` in `manifest.json`:

```json theme={null}
{
  "content_scripts": [
    {
      "matches": ["https://*.github.com/*"],
      "js": ["content.js"],
      "css": ["content.css"],
      "run_at": "document_end"
    }
  ]
}
```

* `"matches"` — the URL patterns where the script runs
* `"js"` — scripts to inject, in order
* `"css"` — stylesheets to inject alongside the scripts
* `"run_at"` — when to inject: `"document_start"` (before DOM), `"document_end"` (after DOM parsed), or `"document_idle"` (after load)

## Target specific pages with match patterns

**Single domain**

```json theme={null}
"matches": ["https://www.notion.so/*"]
```

**Subdomain wildcard**

```json theme={null}
"matches": ["https://*.github.com/*"]
```

Matches `github.com`, `gist.github.com`, etc.

**Multiple domains**

```json theme={null}
"matches": [
  "https://twitter.com/*",
  "https://x.com/*"
]
```

**All HTTPS pages**

```json theme={null}
"matches": ["https://*/*"]
```

Use sparingly — running on every page consumes more memory.

## A minimal content script

This example highlights all external links on the page:

```javascript theme={null}
// content.js
function highlightExternalLinks() {
  const links = document.querySelectorAll("a[href]");

  links.forEach((link) => {
    const href = link.getAttribute("href");
    if (href.startsWith("http") && !href.includes(location.hostname)) {
      link.style.outline = "2px solid orange";
      link.title = "External link";
    }
  });
}

// Run after the DOM is ready
if (document.readyState === "loading") {
  document.addEventListener("DOMContentLoaded", highlightExternalLinks);
} else {
  highlightExternalLinks();
}
```

## Communicate with the background service worker

Content scripts cannot use the full `chrome.*` API directly. Send a message to the background worker to trigger privileged operations and receive a response:

```javascript theme={null}
// content.js — send a message and handle the response
chrome.runtime.sendMessage({ type: "GET_USER_PREFS" }, (response) => {
  if (response && response.prefs) {
    applyPreferences(response.prefs);
  }
});
```

```javascript theme={null}
// background.js — listen and respond
chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
  if (message.type === "GET_USER_PREFS") {
    chrome.storage.sync.get(["prefs"], ({ prefs }) => {
      sendResponse({ prefs });
    });
    return true; // Keep the channel open for the async response
  }
});
```

## Limitations

Content scripts run in an isolated world — they share the DOM with the page, but not the page's JavaScript scope.

> **No access to page JavaScript variables**
>
> You cannot read variables or call functions defined by the page's own scripts. To interact with page-level JavaScript, inject a `<script>` tag into the DOM and use `window.postMessage` to communicate.

> **Limited chrome.* API access*\*
>
> Content scripts can use `chrome.runtime`, `chrome.storage`, and `chrome.i18n`, but not `chrome.tabs`, `chrome.windows`, or `chrome.alarms`. Use message passing to the background worker for those.

> **No direct background page access**
>
> Content scripts cannot directly import from or call functions in `background.js`. All communication must go through `chrome.runtime.sendMessage` or `chrome.runtime.connect`.

> **CSP restrictions on fetch**
>
> Some sites set strict Content Security Policies that block `fetch()` calls to external origins from content scripts. Move cross-origin requests to the background service worker.

## Prompt Extensy to create content script behavior

Describe what you want the script to do on the page in your prompt:

> "On every GitHub pull request page, add a button next to the PR title that copies the branch name to the clipboard."

> "Inject a floating sidebar into all pages on `docs.mycompany.com` that shows a table of contents based on the `<h2>` headings on the page."

> "On YouTube video pages, show a small badge over the thumbnail in search results if the video is longer than 20 minutes."

Extensy will generate the content script, register it in `manifest.json` with the appropriate match patterns, and wire up any DOM interactions.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.