| name | content-scripts-and-messaging |
| description | Design how Chromium extensions interact with page DOM and coordinate between content scripts, service workers, and extension pages. Use when deciding how to inject code, pass messages, structure isolated-world logic, or debug cross-context state flow. |
Content Scripts and Messaging
Overview
Keep DOM-touching logic thin and page-specific. Content scripts should own page
reads, mutations, and lightweight page-local behavior; the service worker
should coordinate browser APIs and shared extension state; extension pages
should own user-facing state and workflows. Messaging exists to connect these
contexts, not to hide unclear architecture.
Read these references as needed:
references/content-scripts.md for isolated-world behavior and page access
references/injection-strategies.md for choosing static, dynamic, or runtime
injection
references/messaging-topologies.md for request/response and long-lived
channels
Core workflow
1) Choose the injection model deliberately
- Use static
content_scripts when the code belongs on a stable set of pages.
- Use dynamically registered scripts when matches change at runtime.
- Use
chrome.scripting.executeScript() for user-triggered or event-triggered
injection that should not be always-on.
2) Keep boundaries clear
- Let content scripts translate between the page DOM and extension-safe data.
- Keep privileged browser API calls out of page context.
- Avoid letting a content script become the hidden app controller for the whole
extension.
3) Pick the smallest message topology that works
- Use one-shot messages for simple requests and responses.
- Use long-lived ports only when the feature truly streams state or requires
session-style coordination.
- Keep message payloads structured and versionable; do not send ad hoc strings.
4) Plan for reconnects and duplicate injection
- Assume a tab can reload, a frame can disappear, or the worker can restart.
- Make injection idempotent so repeated runs do not duplicate UI or listeners.
- Clean up observers, timers, and DOM markers when the page lifecycle changes.
5) Validate permission and host assumptions
- Confirm the extension has the host access required for each injection path.
- If the feature should only run after user intent, prefer
activeTab over
broad static host permissions.
Strong defaults
- Use the service worker as the canonical broker for privileged actions.
- Treat content scripts as adapters between page state and extension state.
- Normalize message shapes into a small set of typed operations.
- Mark injected DOM roots clearly to avoid duplicate mounting.
Anti-patterns
- Injecting broad page logic everywhere because it is easier than choosing
matches carefully.
- Sending raw HTML or unstable DOM fragments across contexts.
- Creating port-based protocols when a one-shot message would do.
- Reading privileged extension state directly from page context.
Notes
- Primary references:
https://developer.chrome.com/docs/extensions/develop/concepts/content-scripts
https://developer.chrome.com/docs/extensions/mv3/messaging
https://developer.chrome.com/docs/extensions/reference/scripting/
- Re-check Chrome docs when injection APIs or messaging behavior may have
changed.