How to Build a Chrome Extension with Vue

Share this article

How to Build a Chrome Extension with Vue

In this tutorial, I’m going to show you how to build a simple Vue Chrome extension. Our extension will alter the behavior of the new tab page. For the JavaScript part of the extension, I’ll be using the Vue.js framework, as it will allow us to get up and running quickly and is a lot of fun to work with.

Browser extensions are small programs that can modify and enhance the functionality of a web browser. They can be used for a variety of tasks, such as blocking ads, managing passwords, organizing tabs, altering the look and behavior of web pages, and much more.

The good news is that browser extensions aren’t difficult to write. They can be created using the web technologies you’re already familiar with — HTML, CSS and JavaScript — just like a regular web page. However, unlike regular web pages, extensions have access to a number of browser-specific APIs, and this is where the fun begins. You’re probably already using browser extensions in your web development workflow.

The code for this tutorial can be found on GitHub.

The Basics of a Chrome Extension

The core part of any Chrome extension is a manifest file and a background script. The manifest file is in a JSON format and provides important information about an extension, such as its version, resources, or the permissions it requires. A background script allows the extension to react to specific browser events, such as the creation of a new tab.

To demonstrate these concepts, let’s start by writing a “Hello, World!” Chrome extension.

Make a new folder called hello-world-chrome and two files: manifest.json and background.js:

mkdir hello-world-chrome
cd hello-world-chrome
touch manifest.json background.js

Open up manifest.json and add the following code:

{
  "name": "Hello World Extension",
  "version": "0.0.1",
  "manifest_version": 2,
  "background": {
    "scripts": ["background.js"],
    "persistent": false
  }
}

The name, version and manifest_version are all required fields. The name and version fields can be whatever you want; the manifest version should be set to 2 (as of Chrome 18).

The background key allows us to register a background script, listed in an array after the scripts key. The persistent key should be set to false unless the extension uses chrome.webRequest API to block or modify network requests.

Now let’s add the following code to background.js to make the browser say hello when the extension is installed:

chrome.runtime.onInstalled.addListener(() => {
  alert('Hello, World!');
});

Finally, let’s install the extension. Open Chrome and enter chrome://extensions/ in the address bar. You should see a page displaying the extensions you’ve installed.

As we want to install our extension from a file (and not the Chrome Web Store) we need to activate Developer mode using the toggle in the top right-hand corner of the page. This should add an extra menu bar with the option Load unpacked. Click this button and select the hello-world-chrome folder you created previously. Click Open and you should see the extension installed and a “Hello, World!” popup appear.

Hello World Extension

Congratulations! You just made a Chrome extension.

Overriding Chrome’s New Tab Page

The next step will to have our extension greet us when we open up a new tab. We can do this by making use of the Override Pages API.

Note: before you progress, please make sure to disable any other extensions which override Chrome’s new tab page. Only one extension at a time may alter this behavior.

We’ll start off by creating a page to display instead of the new tab page. Let’s call it tab.html. This should reside in the same folder as your manifest file and background script:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>My New Tab Page!</title>
</head>
<body>
  <h1>My New Tab Page!</h1>
  <p>You can put any content here you like</p>
</body>
</html>

Next we need to tell the extension about this page. We can do so by specifying a chrome_url_overrides key in our manifest file, like so:

"chrome_url_overrides": {
  "newtab": "tab.html"
}

Finally, you need to reload the extension for the changes to take effect. You can do this by clicking the reload icon for the Hello World extension on Chrome’s extensions page.

Reload the extension

Now, when you open a new tab, you should be greeted by your custom message.

Adding Vue to the Extension

Now we have a very basic implementation of our extension up and running, the time has come to think about what the rest of the desired functionality will look like. When a user opens a new tab, I would like the extension to:

  • Fetch a joke from the wonderful icanhazdadjoke.com.
  • Display that joke in a nicely formatted manner to the user.
  • Display a button for the user to favorite the joke. This will save the joke to chrome.storage.
  • Display a button for the user to list favorited jokes.

You could, of course, do all of this with plain JavaScript, or a library like jQuery — and if that’s your thing, feel free!

For the purposes of this tutorial, however, I’m going to implement this functionality using Vue and the awesome vue-web-extension boilerplate.

Using Vue allows me to write better, more organized code faster. And as we’ll see, the boilerplate provides several scripts that take the pain out of some of the common tasks when building a Vue Chrome extension (such as having to reload the extension whenever you make changes).

vue-web-extension-boilerplate

This section assumes that you have Node and npm installed on your computer. If this isn’t the case, you can either head to the project’s home page and grab the relevant binaries for your system, or you can use a version manager. I would recommend using a version manager.

We’ll also need Vue CLI installed and the @vue/cli-init package:

npm install -g @vue/cli
npm install -g @vue/cli-init

With that done, let’s grab a copy of the boilerplate:

vue init kocal/vue-web-extension new-tab-page

This will open a wizard which asks you a bunch of questions. To keep this tutorial focused, I answered as follows:

? Project name new-tab-page
? Project description A Vue.js web extension
? Author James Hibbard <jim@example.com>
? License MIT
? Use Mozilla's web-extension polyfill? No
? Provide an options page? No
? Install vue-router? No
? Install vuex? No
? Install axios? Yes
? Install ESLint? No
? Install Prettier? No
? Automatically install dependencies? npm

You can adapt your answers to suit your preferences, but the main thing to be certain of is that you choose to install axios. We’ll be using this to fetch the jokes.

Next, change into the project directory and install the dependencies:

cd new-tab-page
npm install

And then we can build our new extension using one of the scripts the boilerplate provides:

npm run watch:dev

This will build the extension into a dist folder in the project root for development and watch for changes.

To add the extension to Chrome, go through the same process as outlined above, making sure to select the dist folder as the extension directory. If all goes according to plan, you should see a “Hello world!” message when the extension initializes.

Vue Chrome Extension Project Setup

Let’s take a minute to look around our new project and see what the boilerplate has given us. The current folder structure should look like this:

.
├── dist
│   └── <the built extension>
├── node_modules
│   └── <one or two files and folders>
├── package.json
├── package-lock.json
├── scripts
│   ├── build-zip.js
│   └── remove-evals.js
├── src
│   ├── background.js
│   ├── icons
│   │   ├── icon_128.png
│   │   ├── icon_48.png
│   │   └── icon.xcf
│   ├── manifest.json
│   └── popup
│       ├── App.vue
│       ├── popup.html
│       └── popup.js
└── webpack.config.js

As you can see, from the config file in the project root, the boilerplate is using webpack under the hood. This is awesome, as this gives us Hot Module Reloading for our background script.

The src folder contains all of the files we’ll be using for the extension. The manifest file and background.js should be familiar, but also notice a popup folder containing a Vue component. When the boilerplate builds the extension into the dist folder, it will pipe any .vue files through the vue-loader and output a JavaScript bundle which the browser can understand.

Also in the src folder is an icons folder. If you look in Chrome’s toolbar, you should see a new icon for our extension (also known as the browser action). This is being pulled from this folder. If you click it, you should see a popup open which displays “Hello world!” This is created by popup/App.vue.

Finally, note a scripts folder containing two scripts — one to remove eval usages to comply with the Content Security Policy of Chrome Web Store and one to package your extension into a .zip file, which is necessary when uploading it to the Chrome Web Store.

There are also various scripts declared in the package.json file. We’ll be using npm run watch:dev for developing the extension and later on npm run build-zip to generate a ZIP file to upload to the Chrome Web Store.

Using a Vue Component for the New Tab Page

Start off by removing the annoying alert statement from background.js.

Now let’s make a new tab folder in the src folder to house the code for our new tab page. We’ll add three files to this new folder — App.vue, tab.html, tab.js:

mkdir src/tab
touch src/tab/{App.vue,tab.html,tab.js}

Open up tab.html and add the following:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>New Tab Page</title>
  <link rel="stylesheet" href="tab.css">
</head>
<body>
  <div id="app"></div>
  <script src="tab.js"></script>
</body>
</html>

Nothing special going on here. This is a simple HTML page which will hold our Vue instance.

Next, in tab.js add:

import Vue from 'vue';
import App from './App';

new Vue({
  el: '#app',
  render: h => h(App)
});

Here we import Vue, pass a selector for the element that we want it to replace with our application, then tell it to render our App component.

Finally, in App.vue:

<template>
  <p>{{ message }}</p>
</template>

<script>
export default {
  data () {
    return {
      message: "My new tab page"
    }
  }
}
</script>

<style scoped>
p {
  font-size: 20px;
}
</style>

Before we can use this new tab page, we need to update the manifest file:

{
  "name":"new-tab-page",
  ...
  "chrome_url_overrides": {
    "newtab": "tab/tab.html"
  }
}

And we also need to have the boilerplate compile our files and copy them over to the dist folder, so that they’re available to the extension.

Alter webpack.config.js like so, updating both the entry and plugins keys:

entry: {
  'background': './background.js',
  'popup/popup': './popup/popup.js',
  'tab/tab': './tab/tab.js'
},
plugins: [
  ...
  new CopyPlugin([
    { from: 'icons', to: 'icons', ignore: ['icon.xcf'] },
    { from: 'popup/popup.html', to: 'popup/popup.html', transform: transformHtml },
    { from: 'tab/tab.html', to: 'tab/tab.html', transform: transformHtml },
    ...
  })

You’ll need to restart the npm run watch:dev task for these changes to take effect. Once you’ve done this, reload the extension and open a new tab. You should see “My new tab page” displayed.

My new tab page

Fetching and Displaying Jokes in Our Vue Chrome Extension

Okay, so we’ve overriden Chrome’s new tab page and we’ve replaced it with a mini Vue app. Now let’s make it do more than display a message.

Alter the template section in src/tab/App.vue as follows:

<template>
  <div>
    <div v-if="loading">
      <p>Loading...</p>
    </div>
    <div v-else>
      <p class="joke">{{ joke }}</p>
    </div>
  </div>
</template>

Change the <script> section to read as follows:

<script>
import axios from 'axios';

export default {
  data () {
    return {
      loading: true,
      joke: "",
    }
  },
  mounted() {
    axios.get(
      "https://icanhazdadjoke.com/",
      { 'headers': { 'Accept': 'application/json' } }
    )
      .then(res => {
        this.joke = res.data.joke;
        this.loading = false;
      });
  }
}
</script>

And finally, change the <style> section to read as follows:

<style>
body {
  height: 98vh;
  text-align: center;
  color: #353638;
  font-size: 22px;
  line-height: 30px;
  font-family: Merriweather,Georgia,serif;
  background-size: 200px;
  display: flex;
  align-items: center;
  justify-content: center;
}

.joke {
  max-width: 800px;
}
</style>

If you’re running the npm run watch:dev task, the extension should automatically reload and you should see a joke displayed whenever you open a new tab page.

How does a French skeleton say hello? Bone-jour

Once you’ve verified it’s working, let’s take a minute to understand what we’ve done.

In the template, we’re using a v-if block to either display a loading message or a joke, depending on the state of the loading property. Initially, this will be set to true (displaying the loading message), then our script will fire off an Ajax request to retrieve the joke. Once the Ajax request completes, the loading property will be set to false, causing the component to be re-rendered and our joke to be displayed.

In the <script> section, we’re importing axios, then declaring a couple of data properties — the aforementioned loading property and a joke property to hold the joke. We’re then making use of the mounted lifecycle hook, which fires once our Vue instance has been mounted, to make an Ajax request to the joke API. Once the request completes, we update both of our data properties to cause the component to re-render.

So far, so good.

Persisting Jokes to Chrome’s Storage

Next, let’s add some buttons to allow the user to favorite a joke and to list out favorited jokes. As we’ll be using Chrome’s storage API to persist the jokes, it might be worth adding a third button to delete all of the favorited jokes from storage.

First, add the following to the bottom of manifest.json:

"permissions": [ "storage" ]

This gives the Vue Chrome extension access to the chrome.storage API.

Reload the extension, then add the buttons to the v-else block:

<div v-else>
  <p class="joke">{{ joke }}</p>

  <button @click="likeJoke" :disabled="likeButtonDisabled">Like Joke</button>
  <button @click="logJokes" class="btn">Log Jokes</button>
  <button @click="clearStorage" class="btn">Clear Storage</button>
</div>

Nothing too exciting here. Note the way that we are binding the like button’s disabled property to a data property on our Vue instance to determine its state. This is because a user shouldn’t be able to like a joke more than once.

Next, add the click handlers and the likeButtonDisabled to our script section:

export default {
  data () {
    return {
      loading: true,
      joke: "",
      likeButtonDisabled: false
    }
  },
  methods: {
    likeJoke(){
      chrome.storage.local.get("jokes", (res) => {
        if(!res.jokes) res.jokes = [];
        res.jokes.push(this.joke);
        chrome.storage.local.set(res);
        this.likeButtonDisabled = true;
      });
    },
    logJokes(){
      chrome.storage.local.get("jokes", (res) => {
        if(res.jokes) res.jokes.map(joke => console.log(joke));
      });
    },
    clearStorage(){
      chrome.storage.local.clear();
    }
  },
  mounted() { ... }
}

Here we’ve declared three new methods to deal with the three new buttons.

The likeJoke method looks for a jokes property in Chrome’s storage. If it’s missing (that is, the user has yet to like a joke), it initializes it to an empty array. Then it pushes the current joke onto this array and saves it back to storage. Finally it sets the likeButtonDisabled data property to true, disabling the like button.

The logJokes method also looks for a jokes property in Chrome’s storage. If it finds one, it loops over all of its entries and logs them to the console.

Hopefully what the clearStorage method does is clear.

Go ahead and try this new functionality in the extension and satisfy yourself that it works.

Listing jokes to the console

Adding Polish to the Vue Chrome Extension

Okay, so that seems to work, but the buttons are ugly and the page is a little plain. Let’s finish off this section by adding some polish to the extension.

As a next step, install the vue-awesome library. This will allow us to use some Font Awesome icons in on our page and make those buttons look a bit nicer:

npm install vue-awesome

Register the library with our Vue app in src/tab/tab.js:

import Vue from 'vue';
import App from './App';
import "vue-awesome/icons";
import Icon from "vue-awesome/components/Icon";

Vue.component("icon", Icon);

new Vue({
  el: '#app',
  render: h => h(App)
});

Now alter the template like so:

<template>
  <div>
    <div v-if="loading" class="centered">
      <p>Loading...</p>
    </div>
    <div v-else>
      <p class="joke">{{ joke }}</p>

      <div class="button-container">
        <button @click="likeJoke" :disabled="likeButtonDisabled" class="btn"><icon name="thumbs-up"></icon></button>
        <button @click="logJokes" class="btn"><icon name="list"></icon></button>
        <button @click="clearStorage" class="btn"><icon name="trash"></icon></button>
      </div>
    </div>
  </div>
</template>

Finally, let’s add some more styling to the buttons and include a picture of everyone’s favorite dad:

<style>
body {
  height: 98vh;
  text-align: center;
  color: #353638;
  font-size: 22px;
  line-height: 30px;
  font-family: Merriweather,Georgia,serif;
  background: url("https://uploads.sitepoint.com/wp-content/uploads/2018/12/1544189726troll-dad.png") no-repeat 1% 99%;
  background-size: 200px;
  display: flex;
  align-items: center;
  justify-content: center;
}

.joke {
  max-width: 800px;
}

.button-container {
  position: absolute;
  right: 0px;
  top: calc(50% - 74px);
}

.btn {
  background-color: #D8D8D8;
  border: none;
  color: white;
  padding: 12px 16px;
  font-size: 16px;
  cursor: pointer;
  display: block;
  margin-bottom: 5px;
  width: 50px;
}

.btn:hover {
  background-color: #C8C8C8;
}

.btn:disabled {
  background-color: #909090;
}
</style>

The extension should reload. Try opening a new tab and you should see something like this.

The finished extension

Uploading the Extension to the Chrome Web Store

Should you want to make your extension available for others to download, you’d do this via the Chrome Web Store.

The first thing you’ll need in order to do this is a Google account, which you can use to sign in to the Developer Dashboard. You’ll be prompted for your developer details, and before you publish your first app, you must pay a one-time $5 developer signup fee (via credit card).

Next, you need to create a ZIP file for your app. You can do this locally by running the npm run build-zip. This will create a dist-zip folder in your project root, containing a ZIP file ready to upload to the Web Store.

For a minimal extension, this is all you’d really need to do. However, before you upload anything, it’s worth reading the official Publish in the Chrome Web Store guide.

Conclusion

And with that, we’re done. In this tutorial, I’ve highlighted the main parts of a Chrome extension and shown how to use the vue-web-extension boilerplate to build a Vue Chrome extension rapidly. We finished off by looking at how to upload an extension to the Web Store and everything that involves.

I hope you enjoyed this tutorial and can use it to get started building Chrome extensions of your own. Let me know if you make anything cool.

Frequently Asked Questions (FAQs) on Building a Vue Chrome Extension

What are the prerequisites for building a Vue Chrome Extension?

Before you start building a Vue Chrome Extension, you need to have a basic understanding of Vue.js and its ecosystem. You should be familiar with Vue CLI, Vue Router, and Vuex. Also, you need to have Node.js and npm installed on your computer. Lastly, you should have a basic understanding of Chrome Extensions and their manifest files.

How can I debug my Vue Chrome Extension?

Debugging a Vue Chrome Extension is similar to debugging a regular web application. You can use the Vue.js devtools extension for Chrome. This tool allows you to inspect your Vue components, Vuex state, events, and more. You can also use the regular Chrome DevTools for debugging JavaScript code, inspecting network requests, and analyzing performance.

Can I use Vue 3 for building a Chrome Extension?

Yes, you can use Vue 3 for building a Chrome Extension. However, the process might be slightly different compared to Vue 2 due to the changes in Vue 3’s API and architecture. You might need to adjust your code accordingly.

How can I package and distribute my Vue Chrome Extension?

Once you have built your Vue Chrome Extension, you can package it into a .zip file using the “Build” command in Vue CLI. Then, you can upload this .zip file to the Chrome Web Store for distribution. You need to create a developer account on the Chrome Web Store and pay a one-time registration fee.

Can I use other Vue libraries and plugins in my Chrome Extension?

Yes, you can use other Vue libraries and plugins in your Chrome Extension. However, you need to make sure that these libraries and plugins are compatible with the Chrome Extension environment. Some libraries and plugins might not work correctly due to the security restrictions and sandboxing in Chrome Extensions.

How can I handle permissions in my Vue Chrome Extension?

You can handle permissions in your Vue Chrome Extension by declaring them in the manifest file. Chrome provides a set of APIs for requesting and checking permissions. You can use these APIs in your Vue code to handle permissions.

Can I use Vuex in my Vue Chrome Extension?

Yes, you can use Vuex in your Vue Chrome Extension. Vuex is a state management library for Vue.js. It allows you to manage and share state between your Vue components. You can use Vuex in your Chrome Extension just like in a regular Vue application.

How can I handle background tasks in my Vue Chrome Extension?

You can handle background tasks in your Vue Chrome Extension by using background scripts. Background scripts are JavaScript files that run in the background and can listen for browser events, manage state, and communicate with other scripts.

Can I use Vue Router in my Vue Chrome Extension?

Yes, you can use Vue Router in your Vue Chrome Extension. However, you need to use the “hash” mode instead of the “history” mode due to the limitations of the Chrome Extension environment.

How can I test my Vue Chrome Extension?

You can test your Vue Chrome Extension by loading it in Chrome as an unpacked extension. You can also write unit tests for your Vue components using testing libraries like Jest or Mocha. For end-to-end testing, you can use tools like Cypress or Puppeteer.

James HibbardJames Hibbard
View Author

Network admin, freelance web developer and editor at SitePoint.

chrome extensionvuevue-hubvue-tutorialsvue.js
Share this article
Read Next
Get the freshest news and resources for developers, designers and digital creators in your inbox each week