We'll learn how to make our own plugin for Vue.js, and distribute it on NPM for everyone to use.

Plugins are what makes our lives as developers so much more productive. Most of our projects depend on them as they allow us to ship new features with great speed.

As stated in the Official Vue.js documentation, there is no strictly defined scope for a plugin. It simply adds global-level functionality to your project. But they typically fall into these five categories based on the things we are trying to achieve with them:

  1. Add some global methods or properties (e.g. this is what Vuex or vue-router does).
  2. Add one or more global assets (e.g. something like a stylesheet with/or a JavaScript library).
  3. Add some component options by global mixin (e.g. this is what vue-html-to-paper does).
  4. Add some Vue instance methods by attaching them to Vue.prototype (e.g. this is what vue-axios does).
  5. A library that provides an API of its own, while at the same time injecting some combination of the above.

Now that you understand how handy plugins can be and what needs they can fulfill, let’s see how to add one to your project. Then, we’ll learn how to make our own and distribute it on NPM for everyone to use (yes, it’s going to be super fun!).

How to Add a Vue.js Plugin to Your Project.

To use your plugin after you’ve installed it with npm install (or yarn add), you need to go to your main.js file. This is the entry point that drives our Vue application. Import it and call the Vue.use() global method. One word of caution though: All plugins must instantiated before you start your app with new Vue().

import Vue from "vue";
import YourPlugin from "yourplugin";


new Vue({
// [...]

There is also another way to add a new plugin when the package author allows it: dropping the CDN link in your header’s script tag.

<script src="https://cdn.jsdelivr.net/npm/yourplugin@latest/dist/yourplugin.min.js"></script>

Sometimes, you would like to customize how a plugin behaves. You can easily do so by passing some options to it when calling Vue.use(). Here is how it works:

Vue.use(YourPlugin, {
 someOption: false,
 anotherOption: false

For instance with vue-chartist, you can choose the text to display when no data is available to properly draw the chart as follows:

Vue.use(VueChartist, {
 messageNoData: "You have not enough data"

Now let’s get back to the main event — building your first Vue.js plugin together.

How to Build Your Own Vue.js Plugin from Scratch

If you are reading this, you are probably a frontend developer like me. And like any other frontend developer, you probably love having nice handsome buttons for your interfaces! So that’s what we’ll be building: a bunch of nice handsome buttons that we’ll be able to reuse. This will save us a lot of time for future projects! You’ll also have the knowledge to package all your remaining base components and why not release your own design system?

Step 1: Initializing the Plugin Structure

Let’s create an empty folder for our package and initialize NPM. This will generate a new package.json file. We’ll deal with it later.

$ mkdir nice-handsome-button && cd nice-handsome-button
$ npm init
# The command above will create a new package.json
# Press enter to answer all the following questions

Add a new folder called src at the root, in which you create a new file NiceHandsomeButton.vue. You can rapidly prototype with just a single *.vue file with the vue serve and vue buildcommands, but they require an additional global addon to be installed first:

npm install -g @vue/cli
npm install -g @vue/cli-service-global

Now if you run:

$ vue serve NiceHandsomeButton.vue

Visit http://localhost:8080/. A blank page should appear in your browser. Let’s work on our button component from now on! ‍‍

You can read more about @vue/cli-service-global in the official documentation. This addon is that it is quite useful for working on a single .vue file without scaffolding an entire project with vue create my-new-project.

Step 2: Working on Our Handsome Button Component


As this tutorial is not about learning how to write Vue components, I expect you to be familiar with the basics. The full code of our nice handsome button is available below (the template, the JavaScript logic and the style). Copy it, open NiceHandsomeButton.vue and paste the content inside.

'nice-handsome-button--' + color,
'nice-handsome-button--' + size,
'nice-handsome-button--rounded': rounded

We have kept things simple, but here are a few things to note:

  • I am using BEM. If you are not familiar with it, please read this now: MindBEMding — getting your head 'round BEM syntax.
  • I added the props color, size and rounded. As their names indicate, they will allow us to control the color, the size and whether or not our button should be rounded.
  • I’m also using a slot for the content so that we can use it like a normal button <nice-handsome-button>My Button Label</nice-handsome-button>.


Let’s define the props our component can accept as well as the two methods that will emit an event when we click/double-click on it.

export default {
 props: {
  color: {
   type: String,
   default: "blue",
   validator(x) {
     return ["blue", "green", "red"].indexOf(x) !== -1;
  rounded: {
   type: Boolean,
   default: true
  size: {
   type: String,
   default: "default",
   validator(x) {
    return ["small", "default", "large"].indexOf(x) !== -1;

 methods: {
  onClick(event) {
   this.$emit("click", event);

  onDoubleClick(event) {
   this.$emit("dblclick", event);


Last but not least, let’s style our component. ‍

.nice-handsome-button {
 display: inline-block;
 outline: 0;
 border: 1px solid rgba(0, 0, 0, 0.1);
 color: #ffffff;
 font-weight: 500;
 font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
 user-select: none;
 cursor: pointer;

/* --> COLORS <-- */

.nice-handsome-button--blue {
 background-color: #0194ef;

.nice-handsome-button--green {
 background-color: #1bb934;

.nice-handsome-button--red {
 background-color: #e1112c;

/* --> SIZES <-- */

.nice-handsome-button--small {
 padding: 8px 10px;
 border-radius: 4px;
 font-size: 12px;
 line-height: 12px;

.nice-handsome-button--default {
 padding: 12px 14px;
 border-radius: 6px;
 font-size: 14px;
 line-height: 16px;

.nice-handsome-button--large {
 padding: 16px 18px;
 border-radius: 8px;
 font-size: 16px;
 line-height: 20px;

/* --> BOOLEANS <-- */

.nice-handsome-button--rounded {
 border-radius: 60px;

Our component is now ready to use and can be used like this:

<nice-handsome-button :rounded="true" color="red" size="large">My Button</nice-handsome-button>

Let’s package it now.

Step 3: Write the Install Method

Before we start this section, let’s create an index.js file in your src folder.

Remember that Vue.use() global we talked about earlier? Well… what this function does is call the install() method that we will define now.

This function takes two parameters: the Vue constructor and the options object that a user can set. You can skip the last argument if you don’t need it as it is optional. But if you want to make your plugin customizable, this is where you will catch the different parameters:

 param: "something"

// Then in your install method options.param will equal to "something"

In index.js, let’s import our component and define our install method.

import NiceHandsomeButton from "./NiceHandsomeButton.vue";

export default {
 install(Vue, options) {
  // Let's register our component globally
  // https://vuejs.org/v2/guide/components-registration.html
  Vue.component("nice-handsome-button", NiceHandsomeButton);

Congratulations, you almost made it!

Step 4: Reworking package.json

Open your package.json file that you created when running npm init.

 "private": false,
 "name": "nice-handsome-button",
 "version": "0.0.1",
 "description": "A nice handsome button you will love",
 "author": "Nada Rifki",
 "license": "MIT",
 "main": "./dist/index.cjs.js",
 "scripts": {
  "dev": "vue serve NiceHandsomeButton.vue",
  "build": "bili --name index --plugin vue --vue.css false"
 "files": [

A few notes:

  • private is set to false. This means your package is public (i.e. everyone is able to see and install it).
  • Choose a name for your package. You have to make sure that it’s not already taken.
  • The version number is set to 0.0.1. You will have to increment this number every time you publish an update for your package. If you are not familiar with semantic versioning, I highly recommend you read this.
  • Choose a description that describes your package in a few words. This will help other developers understand what pain your plugin solves.
  • The main is the primary entry point to your program. That is, if your package is named foo, and a user installs it, and then does require("foo"), then your main module’s exports object will be returned.
  • The scripts property is a dictionary containing script commands that you can easily run with npm run.
  • The files property specifies which files should be published on NPM. It is usually a bad idea to publish everything. We’ll be using bili, so all files in dist folder should be included.

You can read more about all these properties in the official NPM documentation.

Bundling Your Library

In case you don’t know, bundling is the process of grouping all your code from all your files in your project into one single file. The reason behind is simply to increase performance. This will also minify the code and do some other cool things.

To do so, we’ll use Bili, a fast and zero-config library bundler that uses Rollup.js under the hood.

Let’s install it.

$ npm install --save-dev bili

# We'll need these two packages to transpile .vue files
# https://bili.egoist.moe/#/recipes/vue-component
$ npm install --save-dev rollup-plugin-vue
$ npm install --save-dev vue-template-compiler

Now, create our bili.config.js file in the root folder and add our bundling settings:

module.exports = {
  banner: true,
  output: {
    extractCSS: false,
  plugins: {
    vue: {
      css: true

All you have left to do is run the command below on your terminal and your package is bundled — it’s as easy as 1-2-3!

$ npx bili

You should obtain a new dist folder with a index.cjs.js file.

By default <style> tag in Vue SFC will be extracted to the same location where the JS is generated but with .css extension. That’s why we added --vue.css false in the command above.

To learn more about Bili and how to customize it, I recommend you take a look at the documentation.

Sharing Your Wonder on NPM

Now that your package is ready, the only thing left for you is to publish your package on NPM.

Start by creating an account on NPM (you can also run npm adduser if you prefer using the command lines). Then go to your terminal and run npm login. You will have to input your username, password and email.

You can check that you are logged in by typing npm whoami. This should display your username.

There is now only one terminal command that stands between you and publishing your package:

$ npm publish

And voilà!

To update your package, just increment the version number in your package.json and rerun npm publish.

How to Use Your Newly Published Library

You can install it like any other package:

$ npm install --save nice-handsome-button

In your main.js, or a similar entry point for your app:

import NiceHandsomeButton from "nice-handsome-button";
import Vue from "vue";


Now, the nice handsome button should be able in any of your .vue files.

<nice-handsome-button :rounded="true" color="red" size="large">My Button</nice-handsome-button>

Where to Go from There?

There is a lot you can do now and that’s awesome! You learned how to package your first component and publish it on NPM for everyone to use. But don’t stop now! Here are a few ideas that may inspire you:

  • Improving this button component by allowing people to set an icon on the left, managing other events like mouseenter or mouseout and so on.
  • Adding new components to this one and releasing a design system.
  • Building a different plugin like a directive or a mixin.

Easy peasy! Finally, we’re done. You can find the plugin’s final code on my GitHub. Feel free to give me your feedback or to reach me on Twitter @RifkiNada if you need help. Enjoy and have a good day!