Why donate
API Explorer
Upgrade Guide
Creating a New Project
The /quasar.config File
Convert q/app-webpack Project
Browser Compatibility
TypeScript Support
Directory Structure
Commands List
CSS Preprocessors
Page Routing with VueRouter
Lazy Loading - Code Splitting
Handling Assets
Boot Files
Prefetch Feature
API Proxying
Handling Vite
Handling import.meta.env
State Management with Pinia
Lint and Format Code
Testing & Auditing
Developing Mobile Apps
Fetching Data
Opening Dev Server To Public
Quasar CLI with Vite - @quasar/app-vite v3
Content Scripts

The content script(s) run in the context of the web page. There will be a new content script instance per tab running the extension.

Communication / Events

You communicate between the BEX parts of your app (background, content scripts & devtools/popup/options page) through our BEX Bridge.

Registering a content script

Your /src-bex/manifest.json is the central point that defines your BEX. This is the place where you also define your content script(s):

/src-bex/manifest.json

"content_scripts": [
  {
    "matches": [ "<all_urls>" ],
    "css": [ "assets/content.css" ],
    "js": [ "my-content-script.ts" ]
  }
]

The generated BEX template uses <all_urls> because its example content script is designed to run on arbitrary pages. Keep broad access only when that is a real requirement of your extension. It increases the extension’s authority and the permission warning shown to users.

For TS devs

Your background and content scripts have the .ts extension. Use that extension in the manifest.json file as well! Examples: “background.ts”, “my-content-script.ts”. While the browser vendors do support only the .js extension, Quasar CLI will convert the file extensions automatically.

Case study

Let’s say we want to react to a button being pressed on our Quasar App and highlight some text on the underlying web page, this would be done via the content scripts like so:

Quasar App, /src

setup () {
  const $q = useQuasar()

  async function myButtonClickHandler () {
    const contentPort = $q.bex.portList.find(portName =>
      portName.startsWith('content@my-content-script-')
    )

    if (contentPort === void 0) {
      $q.notify({ type: 'negative', message: 'Content script is not connected' })
      return
    }

    await $q.bex.send({
      event: 'highlight.content',
      to: contentPort,
      payload: { selector: '.some-class' }
    })
    $q.notify('Text has been highlighted')
  }

  return { myButtonClickHandler }
}
/src-bex/assets/content.css

.bex-highlight {
  background-color: red;
}
/src-bex/my-content-script.js:

/**
 * Importing the file below initializes the content script.
 *
 * Warning:
 *   Do not remove the import statement below. It is required for the extension to work.
 *   If you don't need createBridge(), leave it as "import '#q-app/bex/content'".
 */
import { createBridge } from '#q-app/bex/content'

// The use of the bridge is optional.
const bridge = createBridge({ debug: false })

bridge.on('highlight.content', ({ payload }) => {
  if (typeof payload?.selector !== 'string') return

  let el
  try {
    el = document.querySelector(payload.selector)
  } catch {
    return
  }

  if (el !== null) {
    el.classList.add('bex-highlight')
  }
})

bridge
  .connectToBackground()
  .then(() => {
    console.log('Connected to background')
  })
  .catch(err => {
    console.error('Failed to connect to background:', err)
  })

Content scripts live in an isolated world, allowing a content script to change its JavaScript environment without conflicting with the page or other content scripts.

Isolated worlds do not allow for content scripts, the extension, and the web page to access any variables or functions created by the others. This also gives content scripts the ability to enable functionality that should not be accessible to the web page.