Skip to content

Values and methods for Build Event Handlers

Build Event Handlers have access to the following values and methods.

Build event handler values

constants

Each event includes a constants key.

src/index.ts
import { NetlifyIntegration } from "@netlify/sdk";
const integration = new NetlifyIntegration();
integration.addBuildEventHandler("onPreBuild", ({ constants }) => {
console.log(`The site id is ${constants.SITE_ID}`);
});

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: directory where function source code lives. undefined if no netlify/functions directory exists in the base directory and if not specified by the user.
  • FUNCTIONS_DIST: 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: Netlify site ID.

Along with these constants, Build Event Handlers can also access any of the environment variables that are available in the build environment.

buildConfig

If your build event handler requires additional values from the user, you can specify these requirements with a configuration schema that defines what kind of values these should be. These requirements are defined using Zod. Users can then supply the required values in their netlify.toml configuration file. These values can be used inside of your Build Event Handler, or to create Dynamic Build Event Handlers.

Try the Integration UI

Instead of using buildConfig and asking users to configure your integration in their netlify.toml file, you can instead use the Integration UI to let a user configure your integration from the Netlify UI.

Make a configuration schema

To make a schema using Zod, import { z } from "@netlify/sdk".

Then, use z.object to define what kind of values you expect from the user of your integration. For options, refer to the Zod documentation. Zod automatically ensures that the values a user provides are valid. It also adds typing, so you can be confident while you use the values in your buildConfig object.

src/index.ts
import { NetlifyIntegration, z } from "@netlify/sdk";
const buildConfigSchema = z.object({
output_path: z.string().optional(),
fail_deploy_on_score_thresholds: z.boolean().default(false),
thresholds: z
.object({
performance: z.number(),
accessibility: z.number(),
"best-practices": z.number(),
seo: z.number(),
pwa: z.number(),
})
.optional(),
});
const integration = new NetlifyIntegration({ buildConfigSchema });
integration.addBuildEventHandler("onPreBuild", ({ buildConfig }) => {
console.log(buildConfig.output_path);
console.log(buildConfig.thresholds);
});

A user then supplies the required values in their site’s netlify.toml file:

netlify.toml
[[integrations]]
name = "integration-slug"
[integrations.config]
output_path = "reports/lighthouse.html"
fail_deploy_on_score_thresholds = "true"
[integrations.config.thresholds]
performance = 0.9
accessibility = 0.9
best-practices = 0.9
seo = 0.9
pwa = 0.9

These values are passed into the build event handler’s buildConfig when the build handler is executed.

Dynamic Build Event Handlers

It is possible to hook into Netlify’s build events based on the values in the buildConfig.

To do this, pass an object to the addBuildEventHandler method as the third parameter. The object should contain an if property that resolves to a boolean value. For example, the if property could contain a method that checks buildConfig for a specific value. The Build Event Handler will only run when the if property evaluates to true.

src/index.ts
import { NetlifyIntegration, z } from "@netlify/sdk";
const buildConfigSchema = z.object({
output_path: z.boolean().optional(),
});
const integration = new NetlifyIntegration({ buildConfigSchema });
integration.addBuildEventHandler(
"onPreBuild",
({ buildConfig }) => {
console.log("Hello world from onPreBuild event!");
},
{
if: (buildConfig) => buildConfig.before === true,
}
);
integration.addBuildEventHandler("onPostBuild", () => {
console.log("Hello world from onPostBuild event!");
});

A user then supplies the required values in their site’s netlify.toml file:

netlify.toml
[[integrations]]
name = "integration-slug"
[integrations.config]
before = true

In this example, both onPreBuild and onPostBuild Build Event Handlers will run.

netlifyConfig

When an event executes, a site’s Netlify configuration is normalized by @netlify/config and passed to your build event handler as a netlifyConfig object. Normalization includes applying context-specific or branch-specific settings and combining settings from netlify.toml with build settings configured in the Netlify UI.

After normalization, Build Event Handlers can access and modify most netlifyConfig properties during a site’s build. These include redirects, headers, and build configuration. If a site doesn’t use netlify.toml or build settings selections in the Netlify UI, netlifyConfig and its properties contain default build settings.

Here’s a list of modifiable properties:

Here’s a build event handler code sample that modifies several of the above properties.

src/index.ts
import { NetlifyIntegration } from "@netlify/sdk";
const integration = new NetlifyIntegration();
integration.addBuildEventHandler("onPreBuild", ({ netlifyConfig }) => {
const newCommand = `node YOUR_SCRIPT.js`;
// Run a script after the build command
netlifyConfig.build.command = netlifyConfig.build.command
? `${netlifyConfig.build.command} && ${newCommand}`
: newCommand;
// Modify the build command's environment variables
netlifyConfig.build.environment.DATABASE_URI = getDatabaseUri();
// Add redirects
netlifyConfig.redirects.push({
from: "/ORIGIN_PATH",
to: "/DESTINATION_PATH",
});
// Add headers
netlifyConfig.headers.push({
for: "/YOUR_PATH",
values: { YOUR_HEADER_NAME: "YOUR_HEADER_VALUE" },
});
// Add edge functions
netlifyConfig.edge_functions
? netlifyConfig.edge_functions.push({ path: "/YOUR_PATH", function: "YOUR_EDGE_FUNCTION" })
: (netlifyConfig.edge_functions = [{ path: "/YOUR_PATH", function: "YOUR_EDGE_FUNCTION" }]);
});

packageJson

Each 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 the build event handler. The data fields are normalized to prevent build event handler errors. If the site has no package.json, the argument is an empty object.

To access the packageJson object in your build event handler code, use the following pattern:

src/index.ts
import { NetlifyIntegration } from "@netlify/sdk";
const integration = new NetlifyIntegration();
integration.addBuildEventHandler("onPreBuild", ({ packageJson }) => {
console.log(packageJson);
});

Environment variables

Build Event Handlers can access build environment variables two different ways:

  • process.env: includes all Netlify build environment variables and any variables a user declares using the Netlify UI or netlify.toml. We recommend you use this when you only need to get values during the build process.
  • netlifyConfig.build.environment: includes only the variables a user declares using the Netlify UI or netlify.toml. We recommend you use this when you need to modify values during the build process.

Visit our Forums for a verified Support Guide on how to access environment variables during a site’s build.

Build event handler methods

We’ve provided a number of utilities and API methods to assist you in writing your build event handler.

Utilities

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

  • build: used to report errors or cancel builds
  • status: used to display information in the deploy summary
  • cache: used to cache files between builds
  • run: used to run commands and processes
  • git: used to retrieve Git-related information such as the list of modified, created, or deleted files
src/index.ts
import { NetlifyIntegration } from "@netlify/sdk";
const integration = new NetlifyIntegration();
integration.addBuildEventHandler("onPreBuild", ({ 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, build event handlers should rely on try/catch/finally blocks and use utils.build:

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

  • utils.build.failBuild("YOUR_MESSAGE"): method that fails the build. The build in the user’s dashboard would show “Failed”. Use this to indicate something went wrong.
  • utils.build.failPlugin("YOUR_MESSAGE"): method that fails the build event handler but not the build.
  • utils.build.cancelBuild("YOUR_MESSAGE"): method that cancels the build. The user’s dashboard would show “Canceled” for that build. Use this to indicate that the build is being canceled as planned.

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

  • error: original Error instance. Its stack trace will be preserved and its error message will be appended to the "YOUR_MESSAGE" argument.
src/index.ts
import { NetlifyIntegration } from "@netlify/sdk";
const integration = new NetlifyIntegration();
integration.addBuildEventHandler("onPreBuild", ({ utils }) => {
try {
badMethod();
} catch (error) {
utils.build.failBuild("YOUR_MESSAGE", { error });
}
});

Logging

Anything logged to the console is printed in the build logs.

src/index.ts
import { NetlifyIntegration } from "@netlify/sdk";
const integration = new NetlifyIntegration();
integration.addBuildEventHandler("onPreBuild", () => {
console.log("This is printed in the build logs");
});

If you’d prefer to make the information more prominent for users, use utils.status.show() to display information in the deploy summary instead.

src/index.ts
import { NetlifyIntegration } from "@netlify/sdk";
const integration = new NetlifyIntegration();
integration.addBuildEventHandler("onPreBuild", ({ utils }) => {
utils.status.show({
// Optional. Default to the integration’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 build event handler. 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:

src/index.ts
import { NetlifyIntegration } from "@netlify/sdk";
const integration = new NetlifyIntegration();
integration.addBuildEventHandler("onPreBuild", ({ utils }) => {
try {
await doSomethingAsync();
} catch (error) {
utils.build.failBuild("YOUR_MESSAGE", { error });
}
});

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

src/index.ts
import { NetlifyIntegration } from "@netlify/sdk";
const integration = new NetlifyIntegration();
integration.addBuildEventHandler("onPreBuild", ({ utils }) => {
// Any error thrown inside this function will be shown
// in the build logs as a build event handler bug.
await doSomethingAsync();
});

Build event handlers 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.

src/index.ts
// Example of how to use callbacks.
import { NetlifyIntegration } from "@netlify/sdk";
const { promisify } = require("util");
const integration = new NetlifyIntegration();
// VALID EXAMPLE: please use this.
// This callback will be awaited.
integration.addBuildEventHandler("onPostBuild", ({ utils }) => {
const response = await promisify(doSomethingAsync)();
console.log(response);
});
// INVALID EXAMPLE: do not use this.
// This callback will not be awaited.
integration.addBuildEventHandler("onPostBuild", ({ utils }) => {
doSomethingAsync((error, response) => {
console.log(response);
});
});
src/index.ts
// Example of how to use events.
import { NetlifyIntegration } from "@netlify/sdk";
import pEvent from "p-event";
const integration = new NetlifyIntegration();
// VALID EXAMPLE: please use this.
// This event will be awaited.
integration.addBuildEventHandler("onPreBuild", ({ utils }) => {
const emitter = doSomethingAsync();
emitter.start();
const response = await pEvent(emitter, "response");
console.log(response);
});
// INVALID EXAMPLE: do not use this.
// This event will not be awaited.
integration.addBuildEventHandler("onPreBuild", ({ utils }) => {
const emitter = doSomethingAsync();
emitter.on("response", (response) => {
console.log(response);
});
emitter.start();
});
src/index.ts
// Example of how to use `Array.map()`.
const integration = new NetlifyIntegration();
// VALID EXAMPLE: please use this.
// This callback will be awaited.
integration.addBuildEventHandler("onPostBuild", ({ utils }) => {
await Promise.all(
array.map(async () => {
await doSomethingAsync();
})
);
});
// INVALID EXAMPLE: do not use this.
// This callback will not be awaited.
integration.addBuildEventHandler("onPreBuild", ({ utils }) => {
array.forEach(async () => {
await doSomethingAsync();
});
});