Create Build Plugins
In addition to installing plugins written by others, you can create your own. In addition to the detailed reference information below, we’ve provided a Build Plugin template to get you started.
# Plug into events
Build Plugins run JavaScript code in response to different events happening during the build-deploy lifecycle.
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 and Edge Handlers bundling.onPostBuild
: runs after the build command completes; afteronBuild
tasks, Functions bundling, and Edge Handlers bundling are executed; and before the deploy stage. Can be used to prevent a build from being deployed.onError
: runs when an error occurs in the build or deploy stage, failing the build. Can’t be used to prevent a build from being deployed.onSuccess
: runs when the deploy succeeds. Can’t be used to prevent a build from being deployed.onEnd
: runs after completion of the deploy stage, regardless of build error or success; is useful for resources cleanup. Can’t be used to prevent a build from being deployed.
# 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 data:
- constants: values generated by the plugin event
- inputs: values configured by the plugin user
- netlifyConfig : the site’s Netlify configuration
- packageJson: the contents of the site’s
package.json
file
# 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 nonetlify/functions
directory exists in the base directory and 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.EDGE_HANDLERS_SRC
: the directory where Edge Handlers source code lives.undefined
if noedge-handlers
directory exists in the base directory and if not specified innetlify.toml
.IS_LOCAL
: boolean indicating whether the build was run locally or on NetlifyNETLIFY_BUILD_VERSION
: version of Netlify Build as amajor.minor.patch
stringSITE_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: greeting
description: Describe what greeting does
- name: total
description: Describe what total 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]
greeting = "hello"
total = 2
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.greeting) // hello
console.log(inputs.total) // 2
},
}
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: greeting
description: Describe what greeting does
required: true
- name: total
description: Describe what total does
default: 2
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.
# netlifyConfig
When an event handler executes, a site’s Netlify configuration is passed as an argument in a netlifyConfig
object. @netlify/config normalizes the contents of this object before it’s passed. Normalization includes applying context-specific or branch-specific settings and combining settings from netlify.toml
with build settings configured in the Netlify UI. If a site doesn’t use a netlify.toml
or build settings selections in the Netlify UI, netlifyConfig
contains default build settings.
Don’t rely on custom headers and redirects from netlifyConfig
Although headers
and redirects
properties of netlify.toml
are available in netlifyConfig
, they don’t undergo normalization or transformation. Meanwhile, custom headers defined in _headers
and redirects specified in _redirects
are not included in netlifyConfig
. Your plugin code shouldn’t rely on these values.
To access the netlifyConfig
object in your plugin code, use this pattern:
// index.js
module.exports = {
onPreBuild: ({ netlifyConfig }) => {
console.log(netlifyConfig)
},
}
# packageJson
Each plugin event handler includes a packageJson
argument. When an event handler executes, the contents of the package.json
in a site’s base directory get passed to a plugin. The data fields are normalized to prevent plugin errors. If the site has no package.json
, the argument is an empty object.
To access the packageJson
object in your plugin code, use the following pattern:
// index.js
module.exports = {
onPreBuild: ({ packageJson }) => {
console.log(packageJson)
},
}
# 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 buildsstatus
: to display information in the deploy summarycache
: to cache files between buildsrun
: to run commands and processesgit
: 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. Instead of using the onError
event to handle exceptions, plugins should rely on try
/catch
/finally
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 originalError
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 incorrect code and the way to fix it.
// index.js
// Example showing how to use callbacks.
const { promisify } = require('util')
module.exports = {
// INCORRECT 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 = {
// INCORRECT 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 = {
// INCORRECT 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!')
},
}
}
}
Did you find this doc useful?
Your feedback helps us improve our docs.