Chrome Extension Content Script Injection — Best Practices

3 min read

Content Script Injection Patterns

Content scripts run in the context of web pages. Understanding the different injection methods is essential for building effective Chrome extensions.

Overview

There are three primary ways to inject content scripts:

Static Injection (Manifest)

Declare content scripts in manifest.json:

{
  "content_scripts": [{
    "matches": ["https://*.example.com/*"],
    "js": ["content.js"],
    "css": ["content.css"],
    "run_at": "document_idle"
  }]
}

Best for always-on functionality on specific sites—runs automatically without user interaction.

Programmatic Injection

Use the Scripting API for on-demand injection:

chrome.scripting.executeScript({
  target: { tabId: 123 },
  files: ['content.js']
});

Requires "scripting" permission plus host permission or activeTab. Best for user-triggered actions.

Dynamic Registration

Register scripts at runtime:

chrome.scripting.registerContentScripts([{
  id: 'my-script',
  matches: ['https://*.example.com/*'],
  js: ['content.js'],
  persistAcrossSessions: true
}]);

Ideal for user-configurable site matching. Unregister with:

chrome.scripting.unregisterContentScripts(['my-script']);

run_at Timing

Value Description
"document_start" Before DOM constructed
"document_idle" After DOMContentLoaded (default, recommended)
"document_end" After DOM, before subresources

World Isolation

{
  "content_scripts": [{
    "matches": ["https://*.example.com/*"],
    "js": ["content.js"],
    "world": "MAIN"
  }]
}

CSS Injection

Static: add "css": ["styles.css"] to content_scripts. Programmatic:

chrome.scripting.insertCSS({ target: { tabId: 123 }, css: 'body { }' });
chrome.scripting.removeCSS({ target: { tabId: 123 }, css: 'body { }' });

Injection Guards

Prevent duplicate injections using DOM markers:

if (document.body.hasAttribute('data-extension-injected')) return;
document.body.setAttribute('data-extension-injected', 'true');

Or check registered scripts:

chrome.scripting.getRegisteredContentScripts({ ids: ['my-script'] });

Best Practices

  1. Use static injection for always-on features
  2. Use programmatic injection for user-triggered actions
  3. Use dynamic registration for user-configurable matching
  4. Prefer ISOLATED world for security
  5. Implement injection guards to prevent duplicates

Cross-References

Part of the Chrome Extension Guide by theluckystrike. Built at zovo.one.