Create build plugins

In addition to installing plugins written by others, you can create your own.

Plug in to build events

Build Plugins run JavaScript code in response to different events happening during builds.

For example, the onPreBuild event handler runs before your build command. The onPostBuild event handler runs after your site build has completed. The following event handlers are currently available:

  • onPreBuild: runs before the build command is executed.
  • onBuild: runs directly after the build command is executed and before Functions bundling.
  • onPostBuild: runs after the build command completes and after onBuild tasks and Functions bundling are executed.
  • onSuccess: runs only when the build stage succeeds, but before deploy begins; cannot be used to fail a build.
  • onError: runs only when an error occurs in the build stage, failing the build; before deploy begins.
  • onEnd: runs after completion of the build stage, regardless of build error or success; before deploy begins.

Only the build stage

All currently available event handlers work within the build stage of a deploy, before a deploy object is created or file uploading begins. We plan to add event handlers for other deploy stages in the future.

Anatomy of a plugin

A plugin consists of two files:

  • A manifest.yml file in the package root with the plugin's name at minimum:

    # manifest.yml
    
    name: netlify-plugin-hello-world
    
  • A JavaScript object like so:

    // index.js
    
    module.exports = {
      onPreBuild: () => {
        console.log('Hello world from onPreBuild event!')
      },
    }
    

The plugin defined above will output Hello world from onPreBuild event! right before the site's build command is run.

The index.js file runs in a regular Node.js environment and can use any Node.js core methods and modules. Environment variables can be accessed and modified with process.env.

Store both files together in a single folder. You can store this folder with your site code to run as a local plugin, or you can publish the plugin to npm.

Local plugins

You can run your own custom plugins from within your site repository without publishing to npm. To do this, save your plugin index.js and manifest.yml files into a folder in the repository. Then, using the file-based installation method, enter the path to your plugin folder in the package field.

The following example installs a local plugin stored in /plugins/netlify-plugin-hello-world:

# netlify.toml

[[plugins]]
package = "/plugins/netlify-plugin-hello-world"

Take care with formatting

Each plugin you add to the netlify.toml file must have its own [[plugins]] line. For a local plugin, the package value must start with . or /.

With a local plugin declared, you can verify it's loading correctly by using the Netlify CLI to run the build locally.

Plugin values

When a plugin runs, it can receive certain values in the form of constants, which are generated by the plugin event, and inputs, which are configured by the plugin user.

Constants

Each event handler includes a constants key.

// index.js

module.exports = {
  onPreBuild: ({ constants }) => {
    console.log(constants)
  },
}

The constants key contains the following values:

  • CONFIG_PATH: path to the Netlify configuration file. undefined if none was used.
  • PUBLISH_DIR: directory that contains the deploy-ready HTML files and assets generated by the build. Its value is always defined, but the target might not have been created yet.
  • FUNCTIONS_SRC: the directory where function source code lives. undefined if not specified by the user.
  • FUNCTIONS_DIST: the directory where built serverless functions are placed before deployment. Its value is always defined, but the target might not have been created yet.
  • IS_LOCAL: boolean indicating whether the build was run locally or on Netlify
  • NETLIFY_BUILD_VERSION: version of Netlify Build as a major.minor.patch string
  • SITE_ID: the Netlify site ID

Inputs

If your plugin requires additional values from the user, you can specify these requirements in an inputs array in the plugin's manifest.yml file:

# manifest.yml

name: netlify-plugin-hello-world
inputs:
  - name: foo
    description: Describe what foo does
  - name: fizz
    description: Describe what fizz does

When you or a user install the plugin, the input names are used as keys with user-supplied values in the site netlify.toml file:

# netlify.toml

[[plugins]]
package = "./plugins/netlify-plugin-hello-world"
  [plugins.inputs]
  foo = "bar"
  fizz = "pop"

These inputs values are passed into the plugin when the event handlers are being executed.

To access them in your plugin code you can use the following pattern:

// index.js

module.exports = {
  onPreBuild: ({ inputs }) => {
    console.log('Hello world from onPreBuild event!')
    console.log(inputs.foo) // bar
    console.log(inputs.fizz) // pop
  },
}

Plugin inputs cannot be set through the Netlify UI

Currently, users cannot set inputs when installing plugins from the Netlify UI. If you would like your plugin to be listed in the plugins directory, we recommend setting zero-config defaults where possible, falling back to accepting values from build environment variables if needed.

Input validation

Plugin inputs can be validated using the inputs property in the plugin manifest.yml file:

# manifest.yml

name: netlify-plugin-hello-world
inputs:
  - name: foo
    description: Describe what foo does
    required: true
  - name: fizz
    description: Describe what fizz does
    default: 5

The inputs property is an array of objects with the following members:

  • name {string}: Name of the input. Required.
  • description {string}: Description of the input.
  • required {boolean}
  • default {any}: Default value.

Always use inputs for validation

We recommended using the inputs property to validate your plugin inputs and assign default values. This works more consistently and efficiently than coding your own validation inside event handlers.

Plugin methods

We've provided a number of utilities and API methods to assist you in writing plugins.

Utilities

Several utilities are provided with the utils argument to event handlers:

  • build: to report errors or cancel builds
  • status: to display information in the deploy summary
  • cache: to cache files between builds
  • run: to run commands and processes
  • git: to retrieve Git-related information such as the list of modified/created/deleted files
// index.js

module.exports = {
  onPreBuild: async ({ utils: { build, status, cache, run, git } }) => {
    await run.command('eslint src/ test/')
  },
}

Error reporting

Exceptions thrown inside event handlers are reported in logs as bugs. You should handle errors with try/catch blocks and use utils.build:

// index.js

module.exports = {
  onPreBuild: ({ utils }) => {
    try {
      badMethod()
    } catch (error) {
      return utils.build.failBuild('Failure message')
    }
  },
}

The following methods are available depending on the error's type:

  • utils.build.failBuild('message'): fails the build - the build in your dashboard would show “Failed”. Use this to indicate something went wrong.
  • utils.build.failPlugin('message'): fails the plugin but not the build.
  • utils.build.cancelBuild('message'): cancels the build - the dashboard would show “Cancelled” for that build. Use this to indicate that the build is being cancelled as planned.

utils.build.failBuild(), utils.build.failPlugin() and utils.build.cancelBuild() can specify an options object with the following properties:

  • error: the original Error instance. Its stack trace will be preserved and its error message will be appended to the 'message' argument.
// index.js

module.exports = {
  onPreBuild: ({ utils }) => {
    try {
      badMethod()
    } catch (error) {
      return utils.build.failBuild('Failure message', { error })
    }
  },
}

Logging

Anything logged to the console will be printed in the build logs.

// index.js

module.exports = {
  onPreBuild() {
    console.log('This is printed in the build logs')
  },
}

If you'd prefer to make the information more visible, utils.status.show() can be used to display them in the deploy summary instead.

// index.js

module.exports = {
  onPreBuild({ utils }) {
    utils.status.show({
      // Optional. Default to the plugin's name followed by a generic title.
      title: 'Main title',
      // Required.
      summary: 'Message below the title',
      // Optional. Empty by default.
      text: 'Detailed information shown in a collapsible section',
    })
  },
}

Only one status is shown per plugin. Calling utils.status.show() twice overrides the previous status.

This is meant for successful information. Errors should be reported with utils.build.* instead.

Asynchronous code

Asynchronous code can be achieved by using async methods:

// index.js

module.exports = {
  onPreBuild: async ({ utils }) => {
    try {
      await doSomethingAsync()
    } catch (error) {
      utils.build.failBuild('Failure message', { error })
    }
  },
}

Any thrown Error or rejected Promise that is not handled by utils.build will be shown in the build logs as a plugin bug.

// index.js

module.exports = {
  onPreBuild: async ({ utils }) => {
    // Any error thrown inside this function will be shown in the build logs as a plugin bug.
    await doSomethingAsync()
  },
}

Plugins end as soon as their methods end. Therefore you should await any asynchronous operation. The following examples show invalid code and the way to fix it.

// index.js
// Example showing how to use callbacks.
const { promisify } = require('util')

module.exports = {
  // INVALID EXAMPLE: do not use this.
  // This callback will not be awaited.
  onPreBuild: ({ utils }) => {
    doSomethingAsync((error, response) => {
      console.log(response)
    })
  },

  // VALID EXAMPLE: please use this instead.
  // This callback will be awaited.
  onPostBuild: async ({ utils }) => {
    const response = await promisify(doSomethingAsync)()
    console.log(response)
  },
}
// index.js
// Example showing how to use events.
const pEvent = require('p-event')

module.exports = {
  // INVALID EXAMPLE: do not use this.
  // This event will not be awaited.
  onPreBuild: ({ utils }) => {
    const emitter = doSomethingAsync()
    emitter.on('response', response => {
      console.log(response)
    })
    emitter.start()
  },

  // VALID EXAMPLE: please use this instead.
  // This event will be awaited.
  onPreBuild: async ({ utils }) => {
    const emitter = doSomethingAsync()
    emitter.start()
    const response = await pEvent(emitter, 'response')
    console.log(response)
  },
}
// index.js
// Example showing how to use `Array.forEach()`.

module.exports = {
  // INVALID EXAMPLE: do not use this.
  // This callback will not be awaited.
  onPreBuild: ({ utils }) => {
    array.forEach(async () => {
      await doSomethingAsync()
    })
  },

  // VALID EXAMPLE: please use this instead.
  // This callback will be awaited.
  onPostBuild: async ({ utils }) => {
    await Promise.all(
      array.map(async () => {
        await doSomethingAsync()
      }),
    )
  },
}

Dynamic events

Some plugins trigger different events depending on the user's inputs. This can be achieved by returning the plugin object from a function instead.

// index.js

module.exports = function helloWorldPlugin(inputs) {
  if (inputs.before) {
    return {
      onPreBuild: () => {
        console.log('Hello world from onPreBuild event!')
      },
    }
  } else {
    return {
      onPostBuild: () => {
        console.log('Hello world from onPostBuild event!')
      },
    }
  }
}