How to write and test a serverless plugin
Tags: aws, serverless, beginners, javascript
Published: 2019-12-17 09:44:45

---
title: How to write and test a serverless plugin
published: true
description: sharing some tips after building my first serverless plugin
cover_image: https://thepracticaldev.s3.amazonaws.com/i/kzmdpg4utrpmeptql34w.png
tags: #aws, #serverless, #beginners#, #javascript
---


If you are building serverless applications, and more precisely **if you are building applications with the Serverless framework** you will inevitably use some plugins, which are nothing more than _javascript code that extends, overwrites or add new functionalities to the framework_.

Some of the plugins that we often use are for example [Offline](https://github.com/dherault/serverless-offline) which allow to emulate Lambda and API Gateway for local development, or the [WarmUp](https://serverless.com/plugins/serverless-plugin-warmup/) which _"solves"_ [the problem of Lambda coldstarts ](https://dev.to/dvddpl/warming-up-your-lambdas-schedule-or-plugin--flo) or [Split Stacks ](https://serverless.com/plugins/serverless-plugin-split-stacks/) which migrates Cloud Formation resources to nested stack to work around CF resource limit. 

When we started using AWS and Serverless I was looking at plugins like something whose code must have been very difficult or complex.

####they are "hacking" the framework core!!

![plugins are amazing](https://media.giphy.com/media/3o6wrzcyVdjrEC5UaY/giphy.gif)

Actually, when you look at the source code you see that the implementation is often very very simple (the scope of a single plugin should be very limited - therefore mostly it is just a couple of methods using some code from AWS SDK ( or other providers´) 

There are already thousands of [community-contributed plugins](https://serverless.com/plugins/) available, so often is just a matter of realizing what you need in your project and [googling for the right plugin ](https://dev.to/dvddpl/often-your-solution-is-one-more-google-search-away-574a) but sometimes you might want to experiment or add some custom functionality specific to your company or project.

Recently we realized that a "simple shell script" that we wrote to automate some tasks after `sls deploy` was completed, grew - as often happens - to a hundred lines.
Therefore we decided to give custom plugins a go.

If this is your case a very good start are the articles on the serverless website:

-  [https://serverless.com/blog/writing-serverless-plugins/](Writing Serverless Plugins)
- [https://serverless.com/blog/writing-serverless-plugins-2/](Writing Serverless Plugins Part II)
- [https://serverless.com/blog/advanced-plugin-development-extending-the-core-lifecycle/](Extending the core lifecycle) 

So I will not bore you with another basic tutorial but rather share some small aspects which at first slowed me down in the development of our first plugin.

##How to run and test a plugin that I am developing?

To use a serverless plugin you just run `npm install` to add it to your package.json dependencies and then add it to the ´plugins´ section of your serverless.yml file

To use a plugin that has not yet been published you can specify the folder where you have it:

```
plugins
 - myplugins/my-first-awesome-serverless-plugin
```
or 

```
plugins
  - ./my-first-awesome-serverless-plugin
```
Just be aware that passing a local path outside your root will not work though

```
plugins
  - ../../my-first-awesome-serverless-plugin  #this won't work
```

If you don´t intend ever to publish it and it will always reside alongside your project codebase then just put it into the suggested default folder `.serverless_plugins`

and import it normally as if it was coming from a published package

```
plugins
 - my-first-awesome-serverless-plugin
```

If on the other hand you know that your plugin will be reused and you want to publish it to npm ( either public or - like in our case - under a scoped private registry) you can keep it in a different folder outside and either use `npm link`(even though I always found [npm link](https://docs.npmjs.com/cli/link.html) a bit cumbersome).

The advantage is that you can install the package as usual and nothing will change when you release the plugin and commit your serverless project

```
plugins
 - my-first-awesome-serverless-plugin
```

Another option is just installing the package from a local directory 
``` npm install -D ../../my-first-awesome-serverless-plugin```

This will add the dependency to your package.json and create a symlink to your folder (but you have to remember to change the dependency in the package json to the real publish one as soon as you have publish it.

One last thing, in case you have a scoped package just remember that you need to use quotes to import the plugin in your yml, otherwise you will get a formatting exception:

```
  - "@yourscope/your-plugin"
```


## Lifecycle Events, Hooks and Commands

A Command is the functionality you want to add to your plugin.

Lifecycle events are fired sequentially during a Command. They are basically **the logical steps** in its process/functionality.

Hooks **bind your code** to that specific stage of the process.
 
You can define your commands in the constructor of your plugin:


```
 this.commands = {
            setDomainName: {
                usage: "Sets Hosted UI Domain Name used by Cognito User Pool App Integration",
                lifecycleEvents: ['set'],
                options: {
                    domainName: {
                        usage:
                        'Specify the domain name you want to set ' +
                        '(e.g. "--domain-name \'my-app\'" or "-dn \'my-app\'")',
                        required: true,
                        shortcut: 'dn',
                    },
                },
            },
        };
```

And you can specify the code that must be executed in the hooks section of the constructor:

```
        this.hooks = {
            'setDomainName:set':  this.addDomainName.bind(this)
        };
```

> remember to compose CommandName:LifecycleEvent when creating the key name in your hooks

Your command will be shown when you run `sls help` and you can invoke it simply by `sls yourCommand --yourparams`

If you don´t need your own command but you´d just rather override or add some functionality on existing lifecycle events, just specify the hook section binding your code to the event:

```
 this.hooks = {
      'before:package:finalize':  this.myCustomCode.bind(this)
    }
```

## Plugin Context 

The plugin always receives in the constructor `options` and `serverless` params. serverless being the global service configuration. 

At the beginning I found very useful logging the entire serverless param to understand all the props I had access to:


```
console.log('Serverless instance: ', this.serverless);
// use the normal console  otherwise you will get an error
this.serverless.cli.log(JSON.stringify(this.serverless), 'Serverless instance: ') // Error: Converting circular structure to JSON
```

And what you have access to is basically everything that you have in your serverless yml file.  You just need to figure out the nesting and the prop names:

```
this.serverless.service.provider.name

this.serverless.service.resources.Resources.IdentityPool.Properties.IdentityPoolName
```

You can find more info [here](https://serverless.com/framework/docs/providers/aws/guide/plugins/)


Something that you often see in many plugins´ source code and it's definitely something you will need in yours is the stack name:

```
get stackName() {
    return `${this.serverless.service.service}-${this.options.stage}`;
  }
```

and expecially if - like in our case - you have to act on deployed resources you want to know all the resources in your CloudFormation stack:

```
async retrieveOutputs() {
        return this.serverless.getProvider('aws').request(
            'CloudFormation',
            'describeStacks',
            {StackName: this.stackName},
            this.serverless.getProvider('aws').getStage(),
            this.serverless.getProvider('aws').getRegion()
        ).then(described=> described.Stacks[0].Outputs)
    }

```
Just keep in mind that this will work only after the stack has been deployed, so you can use it in a command - but if it´s a command meant to be run standalone - it will work only if your stack has been deployed at least once.

## Where to go from here
Define your main method which is bound to a hook and write whatever you need - normally just a bunch of [commands interacting with AWS SDK](https://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/CognitoIdentity.html).
**The content of your plugin can be really everything**, and it really depends on what you need and what you want to achieve.  

As usual, I suggest - to **avoid reinventing the wheel** - [to search for something already available](https://dev.to/dvddpl/often-your-solution-is-one-more-google-search-away-574a) and read the source code. 

![Read the source](https://thepracticaldev.s3.amazonaws.com/i/orhhxzivx6wc3ufijnrb.jpg)
 
It might be exactly what you are looking for, like for example 
a [plugin to empty S3 Buckets before the stack is removed](https://serverless.com/plugins/serverless-s3-remover/) or a [plugin to notify on Slack that the deployment is completed](https://github.com/awkward/serverless-plugin-slack) or it could serve anyway as a starting point ( and a learning base ) for what you want to implement. 


I hope it helps.