Skip to Content
LaunchExt | Chrome Extension Dev Platform (Next.js + Plasmo) 🚀 Read more → 
BlogPlasmo Content Script UI Injection Practical Guide

Plasmo Content Script UI Injection Practical Guide

While exploring the Plasmo framework, I discovered that the with-content-scripts-ui example project is particularly valuable—it comprehensively demonstrates how to inject React UI components into web pages using content scripts. Let’s dive into the implementation details and practical techniques of this project.

Project Overview

with-content-scripts-ui is a Plasmo extension example specifically designed to demonstrate content script UI injection. This project is configured with the necessary permissions to inject UI components on any HTTPS website, making it ideal for learning various injection patterns.

Four Core Injection Patterns

1. Overlay Pattern

This pattern is suitable for creating fixed-position floating components like notifications, toolbars, etc:

// contents/plasmo-overlay.tsx export const config: PlasmoCSConfig = { matches: ["https://www.plasmo.com/*"], css: ["font.css"] } export const getStyle = () => { const style = document.createElement("style") style.textContent = cssText return style }

Key Points:

  • Use getStyle function to inject custom styles
  • Reuse styles through CSS files
  • Fixed positioning, independent of page elements

2. Inline Pattern

The inline pattern is practical when you need to embed components at specific locations within the page:

// contents/plasmo-inline.tsx export const getInlineAnchor: PlasmoGetInlineAnchor = () => document.querySelector(`[href="/#pricing"]`) export const getShadowHostId = () => "plasmo-inline-example-unique-id"

Use Cases:

  • Add action hints next to buttons
  • Insert additional information near links
  • Components that need tight integration with page elements

3. Root Container Pattern

For complex scenarios requiring waiting for target elements to appear, the root container pattern provides better control:

// contents/plasmo-root-container.tsx export const getRootContainer = () => new Promise((resolve) => { const checkInterval = setInterval(() => { const rootContainerParent = document.querySelector(`[href="/docs"]`) if (rootContainerParent) { clearInterval(checkInterval) const rootContainer = document.createElement("div") rootContainerParent.appendChild(rootContainer) resolve(rootContainer) } }, 137) })

Advantages:

  • Supports asynchronous waiting for target elements
  • Customizable container creation logic
  • Suitable for dynamically loaded pages

4. Anchored Overlay Pattern

This pattern combines the flexibility of overlays with the precise positioning of inline components:

// contents/plasmo-overlay-anchor.tsx export const getOverlayAnchor: PlasmoGetOverlayAnchor = async () => document.querySelector(`h1`) export const watchOverlayAnchor: PlasmoWatchOverlayAnchor = (updatePosition) => { const interval = setInterval(() => { updatePosition() }, 420) return () => clearInterval(interval) }

Special Features:

  • Supports dynamic position monitoring
  • Components follow anchor elements as they move
  • Ideal for floating components requiring real-time updates

Technical Implementation Deep Dive

Shadow DOM Style Isolation

Plasmo implements style isolation through Shadow DOM, which is crucial for avoiding conflicts with page styles:

// Automatically created Shadow Host const shadowHost = document.createElement("plasmo-csui") shadowHost.id = getShadowHostId() document.body.appendChild(shadowHost) // Create Shadow Root const shadowRoot = shadowHost.attachShadow({ mode: "open" })

Component Mounting Process

The entire injection process can be summarized in these steps:

  1. Permission Check: Verify if the current page matches configured URL rules
  2. Container Creation: Create appropriate DOM containers based on the pattern
  3. Style Injection: Insert component styles into Shadow DOM
  4. React Rendering: Mount React components using createRoot
  5. Event Binding: Set up necessary interactions and lifecycle management

Configuration Independence

Each content script has independent configurations that don’t interfere with each other:

// Each file can have its own configuration export const config: PlasmoCSConfig = { matches: ["https://www.plasmo.com/*"], run_at: "document_idle" }

Practical Application Scenarios

Scenario 1: Page Enhancement Tools

For example, adding price comparison functionality to e-commerce websites:

// Inject price comparison component on product pages export const getInlineAnchor = () => document.querySelector(".product-price")

Scenario 2: Development Assistance Tools

Add debugging panels to websites under development:

// Show debugging tools only in development environment export const config: PlasmoCSConfig = { matches: ["http://localhost:*/*", "http://127.0.0.1:*/*"] }

Scenario 3: Content Annotation Systems

Add commenting and annotation features to documentation websites:

// Add annotation buttons next to article paragraphs export const getOverlayAnchor = () => document.querySelector("article p")

Best Practice Recommendations

1. Choose the Right Injection Pattern

  • Simple Notifications → Overlay Pattern
  • Page Integration → Inline Pattern
  • Dynamic Content → Root Container Pattern
  • Following Elements → Anchored Overlay Pattern

2. Performance Optimization Techniques

// Use debouncing to avoid frequent updates const debouncedUpdate = useDebounce(updatePosition, 100) // Lazy load heavy components const HeavyComponent = React.lazy(() => import('./HeavyComponent'))

3. Error Handling Strategies

export const getRootContainer = () => new Promise((resolve, reject) => { const timeout = setTimeout(() => { reject(new Error("Target element loading timeout")) }, 5000) // ... normal container creation logic })

Frequently Asked Questions

Q: How to resolve style conflicts?

A: Plasmo’s Shadow DOM already provides excellent style isolation. If conflicts persist, you can use CSS Modules or more specific selectors.

Q: How do components communicate with the page?

A: Secure communication can be achieved through window.postMessage, Custom Events, or Plasmo’s Messaging API.

Q: Is the performance impact significant?

A: With proper usage, the impact is minimal. Avoid running complex components on numerous pages and use conditional matching to reduce unnecessary injections.

Q: Does it support Vue or other frameworks?

A: Plasmo is primarily optimized for React, but theoretically supports other frameworks through custom rendering.

Conclusion

The with-content-scripts-ui example project is truly valuable for learning—it showcases Plasmo’s powerful capabilities in UI injection. Each of the four injection patterns has its unique characteristics and can be flexibly chosen based on specific requirements.

In actual development, I’ve found that this modular design significantly simplifies code maintenance. Each feature can be developed and tested independently, greatly improving development efficiency.

If you’re also using Plasmo for browser extension development, I strongly recommend studying this example project carefully. Feel free to share any questions or insights—let’s discuss and learn together!

with-content-scripts-ui Screenshot

Last updated on