Skip to content
Docs

Conditional Build Commands: Environment, Branch, and Custom Workflows

Run a different Vercel build command for each environment or Git branch using a shell script, vercel.json, or vercel.ts, and see how Vercel picks one.

DX Team

Copy link to headingHow to run a different Vercel build command per environment or branch

Vercel runs one build command per deployment, but that command doesn't have to be the same everywhere. Point it at a script that reads the deployment's environment or branch. A single project can then run npm run build:production for production and npm run build:preview for every preview.

Here's how Vercel decides which command to run, and how to make that decision depend on the environment, the Git branch, or your own environment variables.

Copy link to headingHow Vercel resolves your build command

Three places can set the build command, and each one overrides the ones below it:

  1. vercel.json or vercel.ts: A buildCommand property applies to deployments built from that commit, so it can differ per branch.
  2. Project Settings: The Override toggle under Settings > Build and Deployment applies to every deployment in the project.
  3. The framework preset: Vercel picks a default based on the framework you're deploying.

If Next.js is your framework, Vercel looks for a build script in scripts and runs it, and falls back to next build if there isn't one. So npm run build ends up being the effective command even though nothing on Vercel names it.

A dynamic build command is a deliberate use of the top two layers. You override the default with a command that runs a script, and the script decides the rest.

Copy link to headingHow to run different build commands per environment

Start with the two npm scripts your build will choose between, defined in package.json:

package.json
{
"scripts": {
"build:production": "next build",
"build:preview": "echo \"Building a preview\" && next build"
}
}

Next, create a vercel.sh file in your project's root directory that branches on the environment.

Use VERCEL_ENV, which is set to production, preview, or development on every deployment:

vercel.sh
#!/bin/bash
if [[ $VERCEL_ENV == "production" ]] ; then
npm run build:production
else
npm run build:preview
fi

The script can be written in Bash or Node.js, and it has access to every system environment variable Vercel exposes at build time.

If your project uses custom environments such as staging or QA, VERCEL_ENV reports those as preview. Read VERCEL_TARGET_ENV instead, which returns production, preview, development, or the name of the custom environment.

Finally, point the build command at the script. You can do this in your project configuration file or in the dashboard.

Set buildCommand in vercel.json:

vercel.json
{
"$schema": "<https://openapi.vercel.sh/vercel.json>",
"buildCommand": "sh vercel.sh"
}

To do the same in the dashboard, select your project and open Settings > Build and Deployment. Turn on the Override toggle for Build Command and enter sh vercel.sh.

Both routes run the same script. Pick vercel.json when the command should differ per branch, and the dashboard when it shouldn't.

Copy link to headingHow to set a per-branch build command on Vercel

Matching on the branch name varies the build per branch, without configuring a custom environment for each one.

Read VERCEL_GIT_COMMIT_REF, which holds the Git branch of the commit that triggered the deployment:

vercel.sh
#!/bin/bash
if [[ $VERCEL_GIT_COMMIT_REF == "main" ]] ; then
npm run build:production
elif [[ $VERCEL_GIT_COMMIT_REF == "staging" ]] ; then
npm run build:staging
else
npm run build:preview
fi

Use VERCEL_GIT_COMMIT_REF rather than VERCEL_BRANCH_URL for this. VERCEL_BRANCH_URL holds the generated branch domain, such as my-site-git-improve-about-page.vercel.app, so matching on it means parsing a hostname instead of comparing a branch name.

Copy link to headingEnvironment and branch settings in the dashboard

Matching on VERCEL_TARGET_ENV depends on the dashboard configuration. A branch only reports a custom environment's name if that environment tracks it, so open Settings > Environments, select the environment, and set the branch under Branch Tracking. Without that, the branch deploys as a preview and your script takes the preview path.

Copy link to headingBuild command recipes for monorepos and generated config

Three setups need more than an environment or branch check:

  • Switch on your own variable: Add a variable such as BUILD_PROFILE in Settings > Environment Variables, scope it per environment or per branch, and branch on it in vercel.sh. The script then stays stable while the routing lives in the project configuration.
  • Give each monorepo app its own command: Connect each app as a separate Vercel project, set its Root Directory under Settings > Build and Deployment, and give that project its own build command. Each project builds independently from the same repository.
  • Compute the configuration itself: Use vercel.ts instead of vercel.json. It executes at build time rather than being static, so the configuration can be generated rather than hardcoded.

A vercel.ts file exports a typed config object and accepts the same property names as vercel.json:

vercel.ts
import type { VercelConfig } from '@vercel/config/v1';
export const config: VercelConfig = {
buildCommand: 'sh vercel.sh',
};

Use one configuration file or the other. A project should have vercel.ts or vercel.json, not both.

Copy link to headingWhen to skip a build instead of changing the build command

Some cases don't call for a different command at all. To stop a commit from building, use the Ignored Build Step rather than exiting early from vercel.sh. A build command that exits cleanly still produces a deployment, while the Ignored Build Step cancels it.

The Ignored Build Step runs a command of your choosing when the deployment enters the BUILDING state. It runs from the project's Root Directory and has access to system environment variables. An exit code of 1 continues the build, and an exit code of 0 cancels it and sets the deployment state to CANCELED.

Set it under Settings > Build and Deployment, or override it per deployment with ignoreCommand in vercel.json or vercel.ts.

Canceled builds still count as full deployments against your deployment quota and concurrent build slots. For the script patterns and monorepo examples, see the Ignored Build Step guide.

Copy link to headingHow to troubleshoot a Vercel build command that isn't running

When the script doesn't take effect, the cause is usually configuration rather than the script itself:

  • Permission denied when the script runs: Set the build command to shvercel.sh rather than ./vercel.sh. Running the file through sh means its executable bit doesn't need to be committed to Git.
  • The old command still runs: Build and Deployment settings apply from the next deployment onward. Open the Deployments tab and select Redeploy to pick up the change.
  • Every deployment takes the else branch: Your script is reading an empty variable. Open Settings > Environment Variables and select Enable access to System Environment Variables, which exposes VERCEL_ENV and VERCEL_GIT_COMMIT_REF to the build.
  • The script isn't found in a monorepo: A build can't reach files outside its Root Directory. Move vercel.sh inside that directory rather than leaving it at the repository root.
  • Your settings are ignored entirely: A builds array in vercel.json makes Vercel ignore your Build and Deployment settings. Remove builds and use the functions property instead.

If the script runs but takes the wrong branch, echo the variable you're matching on and read the value back in your build logs.

Copy link to headingNext steps

With the build command routing correctly, the same repository can serve preview and production builds from one project. Start a new Vercel project to set this up from the beginning, or browse the templates for a framework-ready starting point.

Copy link to headingFrequently asked questions

Copy link to headingWhat is the difference between the build command and the framework preset?

The framework preset tells Vercel which framework you're deploying and supplies defaults for the build command, output directory, and install command. The build command is one of those defaults. Changing the preset changes several settings at once, while overriding the build command changes only what Vercel runs to build your project.

Copy link to headingCan I override the Vercel build command for a single deployment?

Yes. Add buildCommand to vercel.json or vercel.ts on the branch you're deploying, and it takes precedence over the Build Command in Project Settings. Because the file is versioned alongside your code, each branch can carry its own configuration without any dashboard change.

Copy link to headingWhich environment variable tells my script which branch is building?

Match on VERCEL_GIT_COMMIT_REF, which carries the branch name and is available at both build and runtime. For the environment rather than the branch, use VERCEL_ENV for production, preview, or development, or VERCEL_TARGET_ENV when you need the name of a custom environment, such as staging.

Copy link to headingCan I test my build command locally before deploying?

Run vercel pull to fetch your Project Settings and environment variables, then vercel build to run the same build command Vercel would. Add --target=staging to build against a specific environment, or --prod to use Production environment variables instead of the Preview defaults.