Chrome Extension Development with Vue.js Guide: Build Modern Extensions

19 min read

Chrome Extension Development with Vue.js Guide: Build Modern Extensions

Chrome Extension Development with Vue.js Guide: Build Modern Extensions

Vue.js has become one of the most popular frontend frameworks for building user interfaces, and combining it with Chrome extension development opens up powerful possibilities. If you already know Vue.js, you can leverage your existing skills to create sophisticated Chrome extensions with beautiful, reactive user interfaces. This comprehensive guide will walk you through everything you need to know to build production-ready Chrome extensions using Vue.js in 2025.

Modern Chrome extensions benefit greatly from Vue.js component architecture, reactive data binding, and the extensive ecosystem of Vue tools and libraries. Whether you are building a simple popup extension or a complex devtools panel, Vue.js provides an excellent development experience that scales with your project’s complexity.


Why Use Vue.js for Chrome Extension Development

Building Chrome extensions with traditional HTML, CSS, and JavaScript is certainly possible, but Vue.js offers several compelling advantages that make it an excellent choice for modern extension development. Understanding these benefits will help you make informed decisions about your extension’s architecture.

The Case for Vue.js in Extensions

Vue.js brings component-based architecture to extension development, which is a game-changer for maintainability and code organization. Instead of writing monolithic JavaScript files with tangled logic, you can break your extension into reusable, self-contained components that are easy to understand, test, and maintain. This becomes especially valuable as your extension grows in complexity.

The reactivity system in Vue.js eliminates the manual DOM manipulation that characterized older extension development. When your extension’s state changes, Vue automatically updates the DOM to reflect those changes. This means less code to write and fewer opportunities for bugs to creep in. The difference is particularly noticeable in popup extensions where user interactions need to update multiple parts of the interface simultaneously.

Vue’s single-file component (.vue) format keeps your template, logic, and styles together, making it easy to understand what a component does at a glance. This is far superior to the traditional approach of separating HTML, CSS, and JavaScript into different files, which often leads to maintenance headaches as extensions evolve.

Vue.js Ecosystem Benefits

The Vue.js ecosystem provides excellent tooling that integrates seamlessly with Chrome extension development. Vue CLI and Vite offer fast development servers with hot module replacement, meaning you can see changes instantly as you develop without manually reloading your extension. This developer experience is significantly better than the traditional edit-manifest-json-refresh-extension workflow.

Vue Router enables navigation within your extension if you need multi-page functionality. Pinia, the official state management library, provides a clean way to manage state across different parts of your extension. And VueUse, a collection of utility functions, offers hooks specifically useful for extension development, like accessing browser APIs and managing local storage.

The composition API in Vue 3 deserves special mention for extension development. It allows you to organize logic by feature rather than by option type, making your code more readable and easier to refactor. This becomes invaluable when building complex extensions with multiple features that interact in sophisticated ways.


Setting Up Your Vue.js Chrome Extension Project

The first step in building a Chrome extension with Vue.js is setting up your development environment correctly. This section covers the recommended approach using Vite, which provides the best developer experience and production performance.

Prerequisites and Initial Setup

Before you begin, ensure you have Node.js version 18 or higher installed on your system. You will also need the Chrome browser for testing your extension during development. With these prerequisites in place, you can scaffold your project using Vite with the Vue template.

Create a new project by running the following command in your terminal:

npm create vite@latest my-vue-extension -- --template vue

This command creates a new Vue.js project with Vite as the build tool. Navigate into the project directory and install dependencies:

cd my-vue-extension
npm install

The default Vite + Vue setup is designed for web applications, so you need to configure it specifically for Chrome extension development. This involves adjusting the build configuration and setting up the correct manifest file.

Configuring Vite for Chrome Extensions

The key to making Vite work with Chrome extensions is to configure it to output files that Chrome can load as an extension. Create a new file called vite.config.js in your project root with the following configuration:

import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import { resolve } from 'path'

export default defineConfig({
  plugins: [vue()],
  build: {
    outDir: 'dist',
    rollupOptions: {
      input: {
        popup: resolve(__dirname, 'index.html'),
        options: resolve(__dirname, 'options.html'),
      },
    },
  },
})

This configuration tells Vite to build your extension into a dist directory and specifies multiple entry points for different extension pages. You will need to create separate HTML files for different parts of your extension, such as the popup and options pages.

Creating the Manifest File

Every Chrome extension requires a manifest.json file in the root of your project. This file tells Chrome about your extension’s permissions, files, and capabilities. Create a public folder in your project root and add the manifest.json file there:

{
  "manifest_version": 3,
  "name": "My Vue.js Extension",
  "version": "1.0.0",
  "description": "A Chrome extension built with Vue.js",
  "permissions": [
    "storage",
    "tabs"
  ],
  "action": {
    "default_popup": "index.html",
    "default_icon": {
      "16": "icon16.png",
      "48": "icon48.png",
      "128": "icon128.png"
    }
  },
  "options_page": "options.html",
  "icons": {
    "16": "icon16.png",
    "48": "icon48.png",
    "128": "icon128.png"
  }
}

The manifest_version 3 is the current standard from Google, and it includes important security improvements over version 2. The permissions array specifies which Chrome APIs your extension needs access to, and the action section defines your popup interface.


The popup is the most common interface for Chrome extensions, and building it with Vue.js provides an excellent user experience. This section walks through creating a functional popup with reactive components.

Creating the Popup Component

Your popup component will be the main interface users interact with when they click your extension’s icon. Create a new component in src/components/Popup.vue with the following structure:

<template>
  <div class="popup-container">
    <h1>{{ title }}</h1>
    <div class="counter-section">
      <p>Current count: <strong>{{ count }}</strong></p>
      <div class="button-group">
        <button @click="decrement" class="btn">-</button>
        <button @click="increment" class="btn">+</button>
      </div>
    </div>
    <div class="status">
      <p>Last updated: {{ lastUpdated }}</p>
    </div>
  </div>
</template>

<script setup>
import { ref, onMounted } from 'vue'

const title = 'Vue Extension'
const count = ref(0)
const lastUpdated = ref('')

const increment = () => {
  count.value++
  updateTimestamp()
  saveState()
}

const decrement = () => {
  count.value--
  updateTimestamp()
  saveState()
}

const updateTimestamp = () => {
  lastUpdated.value = new Date().toLocaleTimeString()
}

const saveState = async () => {
  if (chrome?.storage) {
    await chrome.storage.local.set({ count: count.value })
  }
}

onMounted(async () => {
  if (chrome?.storage) {
    const result = await chrome.storage.local.get('count')
    if (result.count !== undefined) {
      count.value = result.count
    }
  }
  updateTimestamp()
})
</script>

<style scoped>
.popup-container {
  width: 300px;
  padding: 16px;
  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
}

.counter-section {
  margin: 20px 0;
}

.button-group {
  display: flex;
  gap: 10px;
  margin-top: 10px;
}

.btn {
  flex: 1;
  padding: 8px 16px;
  background: #42b983;
  color: white;
  border: none;
  border-radius: 4px;
  cursor: pointer;
  font-size: 16px;
}

.btn:hover {
  background: #359268;
}

.status {
  font-size: 12px;
  color: #666;
  margin-top: 16px;
}
</style>

This component demonstrates several key patterns for Vue.js extension development. The script setup syntax provides a clean way to write component logic, while the reactive ref function creates responsive data that automatically updates the DOM when changed.

Connecting to Chrome Storage

One of the most important aspects of extension development is persisting user data across sessions. The example above shows how to use Chrome’s storage API within Vue components. The onMounted lifecycle hook loads saved data when the popup opens, and the saveState function persists changes immediately.

Chrome’s storage API is asynchronous and works differently from localStorage in regular web apps. Always check for the presence of the chrome object before using it, as this ensures your code works both in the extension context and during local development.

Loading the Popup in Development

To test your popup during development, you need to make Vite serve the correct HTML file. Modify your index.html to mount the Vue app correctly:

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Extension Popup</title>
  </head>
  <body>
    <div id="app"></div>
    <script type="module" src="/src/main.js"></script>
  </body>
</html>

Your main.js should mount the Popup component specifically for the popup page. You can use different entry points for different extension pages to keep your bundle sizes small.


Managing State Across Extension Components

Chrome extensions typically have multiple components that need to share state: the popup, background service worker, options page, and content scripts. Vue.js provides excellent patterns for managing this complexity.

Using Pinia for State Management

Pinia is the official state management library for Vue.js, and it works well within Chrome extensions. Create a store to manage your extension’s global state:

// src/stores/extensionStore.js
import { defineStore } from 'pinia'
import { ref, watch } from 'vue'

export const useExtensionStore = defineStore('extension', () => {
  const count = ref(0)
  const userPreferences = ref({
    theme: 'light',
    notifications: true
  })

  const loadState = async () => {
    if (chrome?.storage) {
      const result = await chrome.storage.local.get(['count', 'preferences'])
      if (result.count !== undefined) {
        count.value = result.count
      }
      if (result.preferences) {
        userPreferences.value = result.preferences
      }
    }
  }

  const saveState = async () => {
    if (chrome?.storage) {
      await chrome.storage.local.set({
        count: count.value,
        preferences: userPreferences.value
      })
    }
  }

  watch(count, saveState)
  watch(userPreferences, saveState, { deep: true })

  return {
    count,
    userPreferences,
    loadState,
    saveState
  }
})

This store demonstrates several important patterns. The loadState function retrieves persisted data from Chrome storage when the extension initializes. The saveState function writes changes back to storage. And the watch functions automatically save state whenever it changes, ensuring data persistence without manual intervention.

Communicating Between Components

Chrome extensions have several distinct contexts that need to communicate: the popup, background worker, content scripts, and options page. Vue.js components within these contexts can share state through Chrome’s message passing API.

For communication from your popup to the background worker, use Chrome’s runtime messaging:

// Sending a message from popup
const sendToBackground = async (message) => {
  if (chrome?.runtime) {
    return await chrome.runtime.sendMessage(message)
  }
}

// Listening in background worker
chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
  if (message.type === 'UPDATE_COUNT') {
    // Handle the message
    sendResponse({ success: true })
  }
  return true // Keep channel open for async response
})

This pattern enables sophisticated architectures where your popup can trigger background tasks, and background workers can update popup state in real-time.


Content Scripts with Vue.js

Content scripts run in the context of web pages and can modify page content or extract information. While you cannot directly use Vue components in content scripts (since they run in an isolated world), you can use Vue to build the overlay interfaces that content scripts create.

Creating Injected Interfaces

Content scripts can create DOM elements and then mount Vue applications to those elements. This allows you to build floating panels, tooltips, and other interfaces that appear on web pages:

// content-script.js
const container = document.createElement('div')
container.id = 'vue-injected-panel'
document.body.appendChild(container)

// Mount Vue app to the container
import { createApp } from 'vue'
import InjectedPanel from './components/InjectedPanel.vue'

const app = createApp(InjectedPanel)
app.mount(container)

This approach gives you full Vue.js power for interfaces that live inside web pages. The component receives data from the content script and can communicate back through the message passing API.

Best Practices for Content Script Integration

When building Vue interfaces that inject into web pages, follow these best practices to avoid conflicts with page styles and functionality. Always scope your CSS to prevent your styles from affecting the host page, and vice versa.

Use unique class prefixes or Vue’s scoped styles to ensure style isolation. Consider using shadow DOM for maximum isolation if your extension needs to work on sites with aggressive CSS. Also, be mindful of page performance—mount and unmount Vue applications when they are no longer needed rather than leaving them running.


Building Options Pages with Vue.js

The options page allows users to configure your extension’s behavior. Vue.js makes building a settings interface straightforward, with form handling and state persistence that feels natural.

Creating the Options Component

Create src/components/Options.vue to handle user preferences:

<template>
  <div class="options-page">
    <h1>Extension Settings</h1>
    
    <div class="setting-group">
      <label>
        <input type="checkbox" v-model="settings.enableNotifications" />
        Enable notifications
      </label>
    </div>
    
    <div class="setting-group">
      <label>Theme</label>
      <select v-model="settings.theme">
        <option value="light">Light</option>
        <option value="dark">Dark</option>
        <option value="auto">Auto</option>
      </select>
    </div>
    
    <div class="setting-group">
      <label>Sync interval (minutes)</label>
      <input 
        type="number" 
        v-model.number="settings.syncInterval" 
        min="1" 
        max="60" 
      />
    </div>
    
    <button @click="saveSettings" class="save-btn">
      Save Settings
    </button>
    
    <p v-if="saved" class="success-message">Settings saved!</p>
  </div>
</template>

<script setup>
import { reactive, ref, onMounted } from 'vue'

const settings = reactive({
  enableNotifications: true,
  theme: 'light',
  syncInterval: 5
})

const saved = ref(false)

const saveSettings = async () => {
  if (chrome?.storage) {
    await chrome.storage.sync.set({ settings: { ...settings } })
    saved.value = true
    setTimeout(() => { saved.value = false }, 2000)
  }
}

onMounted(async () => {
  if (chrome?.storage) {
    const result = await chrome.storage.sync.get('settings')
    if (result.settings) {
      Object.assign(settings, result.settings)
    }
  }
})
</script>

<style scoped>
.options-page {
  max-width: 600px;
  margin: 0 auto;
  padding: 24px;
}

.setting-group {
  margin-bottom: 16px;
}

.setting-group label {
  display: block;
  margin-bottom: 8px;
  font-weight: 500;
}

.save-btn {
  background: #42b983;
  color: white;
  border: none;
  padding: 10px 20px;
  border-radius: 4px;
  cursor: pointer;
  font-size: 14px;
}

.success-message {
  color: #42b983;
  margin-top: 12px;
}
</style>

The options page uses Vue’s reactive system to track form changes and automatically saves to Chrome storage when the user clicks save. This pattern scales well for extensions with many configuration options.


Production Build and Deployment

When your extension is ready for production, you need to build it correctly and prepare it for the Chrome Web Store. This section covers the build process and deployment considerations.

Building for Production

Run the Vite build command to create production-ready files:

npm run build

This generates the dist folder with your extension files. Before uploading to the Chrome Web Store, verify that all files are present and that the manifest.json is correctly configured. The build output should include your HTML files, JavaScript bundles, and any assets like icons.

Preparing for Chrome Web Store

Before publishing, create screenshots and a promotional image for your extension’s store listing. Chrome requires specific image sizes, so check the latest requirements in the Chrome Web Store documentation. Write a clear, keyword-rich description that explains what your extension does and who it is for.

Zip the contents of your dist folder (not the folder itself) and upload through the Chrome Web Store Developer Dashboard. After review, your extension will be available to Chrome users worldwide.


Conclusion

Building Chrome extensions with Vue.js combines the best of both worlds: the powerful component architecture and developer experience of Vue.js with the massive reach of the Chrome browser. Throughout this guide, you have learned how to set up a Vue.js project for extension development, create reactive popup interfaces, manage state across extension components, build options pages, and deploy to the Chrome Web Store.

Vue.js is particularly well-suited for extension development because of its gentle learning curve, excellent documentation, and powerful features. Whether you are building a simple productivity tool or a sophisticated developer tool, Vue.js provides the foundation you need to create professional-quality extensions.

As you continue developing Chrome extensions with Vue.js, explore advanced topics like background workers, content script injection patterns, and Chrome API integration. The skills you build with Vue.js extension development transfer directly to building other types of applications, making it an excellent investment in your development career.

Start building your Vue.js Chrome extension today, and join the thousands of developers who are creating extensions that millions of Chrome users rely on every day.

No previous article
No next article