concurrently, npm-run-all, and npm-watch are utility packages designed to manage multiple npm scripts within a project's package.json. While they share the goal of simplifying command execution, they solve different problems. concurrently focuses on running multiple commands at the same time in a single terminal window, making it ideal for starting front-end and back-end servers together. npm-run-all provides a robust way to run scripts either in parallel or in strict sequence, with advanced pattern matching for script names. npm-watch is a specialized tool that watches file changes and automatically triggers specific npm scripts, removing the need for manual restarts during development.
In modern frontend development, a single npm start command often needs to trigger several underlying processes: a bundler, a backend API, a database mock, or a stylesheet compiler. While you could open multiple terminal tabs, this is messy and hard to onboard new developers. The packages concurrently, npm-run-all, and npm-watch solve this by automating script execution, but they approach the problem from different angles. Let's break down how they work and when to use each one.
The core difference lies in how these tools handle time and dependencies between commands.
concurrently runs commands at the same time. It spawns multiple child processes and keeps them alive together. If one process crashes, it can be configured to kill the others, but its main job is simultaneous execution.
// package.json example for concurrently
{
"scripts": {
"dev": "concurrently \"npm run server\" \"npm run client\""
}
}
npm-run-all gives you explicit control over sequence. You can run things in parallel (npm-run-all --parallel) or, more commonly, in a strict chain (run-s) where the next command only starts if the previous one succeeds.
// package.json example for npm-run-all
{
"scripts": {
"build": "run-s clean build:js build:css",
"clean": "rimraf dist",
"build:js": "webpack --mode production",
"build:css": "postcss css/*.css"
}
}
npm-watch is reactive. It doesn't just run a list; it sits idle and watches the file system. When a file changes, it triggers a specific script. It is designed for event-driven workflows rather than linear pipelines.
// package.json example for npm-watch
{
"scripts": {
"watch:css": "npm-watch build:css"
},
"watch": {
"build:css": "css/*.css"
}
}
When running multiple tools, reading the output logs is critical for debugging.
concurrently excels here. It prefixes every line of output with a colored tag identifying which command produced it. This prevents logs from different processes from garbling together into an unreadable mess.
# Output looks like this:
[0] Server listening on port 3000
[1] Client compiled successfully in 200ms
[0] GET /api/users 200 OK
npm-run-all treats output more traditionally. When running in sequence, you see the full output of one command, then the next. In parallel mode, it attempts to group output, but it lacks the sophisticated prefixing and coloring of concurrently, making it harder to distinguish sources in real-time.
# Output is sequential or grouped, less distinct:
> npm run clean
Cleaning dist folder...
> npm run build:js
Building JS...
npm-watch outputs standard logs for the triggered script. Its value isn't in log formatting but in automation. You don't see "watching" logs constantly unless you configure the underlying tool to do so; you just see the build output when a change occurs.
Sometimes you don't know exactly which scripts you need to run, or you have many similar tasks.
npm-run-all supports powerful glob patterns. You can run all scripts that start with a certain prefix without listing them individually. This keeps package.json clean.
// Runs test:unit, test:integration, test:e2e automatically
{
"scripts": {
"test": "run-p test:*"
}
}
concurrently does not support script name patterns. You must explicitly list every command string you want to run. This is verbose if you have ten microservices, but it gives you precise control over arguments for each.
// Must list every service explicitly
{
"scripts": {
"dev": "concurrently \"npm run service-a\" \"npm run service-b\" \"npm run service-c\""
}
}
npm-watch uses a configuration object to map scripts to file paths. It is rigid in structure but very clear about which files trigger which actions.
{
"watch": {
"build:html": "src/html",
"build:js": "src/js"
}
}
How these tools handle failures determines if they are safe for production builds.
npm-run-all is strict. If a command in a sequence fails (returns a non-zero exit code), the entire chain stops immediately. This prevents broken builds from deploying. It is the safest choice for CI/CD pipelines.
// If 'lint' fails, 'build' never runs
{
"scripts": {
"ci": "run-s lint test build"
}
}
concurrently has a --kill-others-on-fail flag. By default, if one process exits, the others keep running. This is great for dev (your API can crash while you fix the frontend), but dangerous for builds if not configured correctly.
// Kills all processes if the server crashes
{
"scripts": {
"dev": "concurrently --kill-others-on-fail \"npm run api\" \"npm run web\""
}
}
npm-watch simply re-runs the script on change. If the script fails, it logs the error and waits for the next file change to try again. It does not stop the watch process itself.
You are building a React app with a Node.js backend. You need both to run, and you want to see logs for both.
concurrently{
"scripts": {
"start:client": "vite",
"start:server": "node server.js",
"dev": "concurrently \"npm run start:client\" \"npm run start:server\""
}
}
You need to clean the folder, lint code, run tests, and then bundle. If linting fails, you must not bundle.
npm-run-allbuild:*) allows scaling without editing the main script.{
"scripts": {
"prebuild": "npm run clean",
"build:js": "webpack",
"build:css": "tailwindcss",
"build": "run-s lint test build:*"
}
}
You are working with legacy Sass or copying static assets. You want the build to run automatically when you save a file.
npm-watchconcurrently for the rest of the app.{
"scripts": {
"watch:assets": "npm-watch",
"dev": "concurrently \"npm run watch:assets\" \"npm run start:app\""
},
"watch": {
"build:assets": "src/assets/**/*"
}
}
Before installing, check the current status of these packages. As of recent ecosystem reviews, npm-run-all has shown signs of stagnation in maintenance. While still widely used and functional, teams starting new enterprise projects should evaluate active alternatives like npm-run-all2 (a fork) or native script features if strict sequence is the only requirement. concurrently and npm-watch remain actively maintained and safe for new projects. Always verify the "last published" date on the npm registry before adding dependencies to critical infrastructure.
| Feature | concurrently | npm-run-all | npm-watch |
|---|---|---|---|
| Primary Goal | Run multiple commands at once | Run commands in sequence or parallel | Run commands on file change |
| Log Output | Colored, prefixed, interleaved | Standard, grouped | Standard, triggered on event |
| Script Patterns | β No (explicit list only) | β Yes (glob support) | β No (config object map) |
| Failure Behavior | Continues (configurable) | Stops immediately (strict) | Retries on next change |
| Best For | Local dev environments | CI/CD and Build pipelines | Asset compilation loops |
Think about your workflow stage:
concurrently. It makes running your full stack feel like a single app. The colored logs are a huge quality-of-life improvement.npm-run-all (or its maintained forks). You need the guarantee that tests pass before the build runs.npm-watch. It automates the boring stuff so you can focus on coding.In many mature projects, you will see concurrently and npm-watch used together: concurrently manages the high-level processes, while one of those processes is a npm-watch task handling specific file changes.
Choose concurrently when you need to start multiple long-running processes (like a React dev server and an Express API) simultaneously in one terminal tab. It is the best fit for local development setups where you want to see logs from all services interleaved and colored for easy reading. Avoid it if you need strict execution order or complex file watching logic.
Choose npm-run-all if your build pipeline requires scripts to run in a specific sequence (e.g., clean -> build -> test) or if you need to run a dynamic list of scripts matching a name pattern. It is superior for CI/CD pipelines and production build steps where process exit codes must be handled strictly. Do not use it for interactive development sessions requiring live log streaming.
Choose npm-watch specifically when you need to trigger a script automatically upon detecting file changes in designated directories. It is perfect for tasks like recompiling Sass, copying assets, or restarting a Node server when source code changes. It is not a general-purpose runner, so pair it with concurrently if you also need to run other non-watched processes.
Run multiple commands concurrently.
Like npm run watch-js & npm run watch-less but better.

Table of Contents
I like task automation with npm
but the usual way to run multiple commands concurrently is
npm run watch-js & npm run watch-css. That's fine but it's hard to keep
on track of different outputs. Also if one process fails, others still keep running
and you won't even notice the difference.
Another option would be to just run all commands in separate terminals. I got tired of opening terminals and made concurrently.
Features:
--kill-others switch, all commands are killed if one diesconcurrently can be installed in the global scope (if you'd like to have it available and use it on the whole system) or locally for a specific package (for example if you'd like to use it in the scripts section of your package):
| npm | Yarn | pnpm | Bun | |
|---|---|---|---|---|
| Global | npm i -g concurrently | yarn global add concurrently | pnpm add -g concurrently | bun add -g concurrently |
| Local* | npm i -D concurrently | yarn add -D concurrently | pnpm add -D concurrently | bun add -d concurrently |
* It's recommended to add concurrently to devDependencies as it's usually used for developing purposes. Please adjust the command if this doesn't apply in your case.
Note The
concurrentlycommand is also available under the shorthand aliasconc.
The tool is written in Node.js, but you can use it to run any commands.
Remember to surround separate commands with quotes:
concurrently 'command1 arg' 'command2 arg'
Otherwise concurrently would try to run 4 separate commands:
command1, arg, command2, arg.
[!IMPORTANT] Windows only supports double quotes:
concurrently "command1 arg" "command2 arg"Remember to escape the double quotes in your package.json when using Windows:
"start": "concurrently \"command1 arg\" \"command2 arg\""
You can always check concurrently's flag list by running concurrently --help.
For the version, run concurrently --version.
Check out documentation and other usage examples in the docs directory.
concurrently can be used programmatically by using the API documented below:
concurrently(commands[, options])commands: an array of either strings (containing the commands to run) or objects
with the shape { command, name, prefixColor, env, cwd, ipc }.
options (optional): an object containing any of the below:
cwd: the working directory to be used by all commands. Can be overridden per command.
Default: process.cwd().shell: shell executable used to run command strings. When unset, uses npm_config_script_shell if present (for example when run via npm run), otherwise cmd.exe on Windows or /bin/sh elsewhere. See shell resolution.defaultInputTarget: the default input target when reading from inputStream.
Default: 0.handleInput: when true, reads input from process.stdin.inputStream: a Readable stream
to read the input from. Should only be used in the rare instance you would like to stream anything other than process.stdin. Overrides handleInput.pauseInputStreamOnFinish: by default, pauses the input stream (process.stdin when handleInput is enabled, or inputStream if provided) when all of the processes have finished. If you need to read from the input stream after concurrently has finished, set this to false. (#252).killOthersOn: once the first command exits with one of these statuses, kill other commands.
Can be an array containing the strings success (status code zero) and/or failure (non-zero exit status).maxProcesses: how many processes should run at once.outputStream: a Writable stream
to write logs to. Default: process.stdout.prefix: the prefix type to use when logging processes output.
Possible values: index, pid, time, command, name, none, or a template (eg [{time} process: {pid}]).
Default: the name of the process, or its index if no name is set.
Templates can wrap any portion of the prefix with {color} and {/color} to restrict coloring to that region (eg [{color}{name}{/color}] colors only the name, leaving the brackets uncolored). If either marker is omitted the missing side is implicit, so a template with no markers is colored in full.prefixColors: a list of colors or a string as supported by Chalk and additional style auto for an automatically picked color.
Supports all Chalk color functions: #RRGGBB, bg#RRGGBB, hex(), bgHex(), rgb(), bgRgb(), ansi256(), bgAnsi256().
Functions and modifiers can be chained (e.g., rgb(255,136,0).bold, black.bgHex(#00FF00).dim).
If concurrently would run more commands than there are colors, the last color is repeated, unless if the last color value is auto which means following colors are automatically picked to vary.
Prefix colors specified per-command take precedence over this list.prefixLength: how many characters to show when prefixing with command. Default: 10raw: whether raw mode should be used, meaning strictly process output will
be logged, without any prefixes, coloring or extra stuff. Can be overridden per command.successCondition: the condition to consider the run was successful.
If first, only the first process to exit will make up the success of the run; if last, the last process that exits will determine whether the run succeeds.
Anything else means all processes should exit successfully.restartTries: how many attempts to restart a process that dies will be made. Default: 0.restartDelay: how many milliseconds to wait between process restarts. Default: 0.timestampFormat: a Unicode format
to use when prefixing with time. Default: yyyy-MM-dd HH:mm:ss.SSSadditionalArguments: list of additional arguments passed that will get replaced in each command. If not defined, no argument replacing will happen.Returns: an object in the shape
{ result, commands }.
result: aPromisethat resolves if the run was successful (according tosuccessConditionoption), or rejects, containing an array ofCloseEvent, in the order that the commands terminated.commands: an array of all spawnedCommands.
Example:
const concurrently = require('concurrently');
const { result } = concurrently(
[
'npm:watch-*',
{ command: 'nodemon', name: 'server' },
{ command: 'deploy', name: 'deploy', env: { PUBLIC_KEY: '...' } },
{
command: 'watch',
name: 'watch',
cwd: path.resolve(__dirname, 'scripts/watchers'),
},
],
{
prefix: 'name',
killOthersOn: ['failure', 'success'],
restartTries: 3,
cwd: path.resolve(__dirname, 'scripts'),
},
);
result.then(success, failure);
CommandAn object that contains all information about a spawned command, and ways to interact with it.
It has the following properties:
index: the index of the command among all commands spawned.
command: the command line of the command.
name: the name of the command; defaults to an empty string.
cwd: the current working directory of the command.
env: an object with all the environment variables that the command will be spawned with.
killed: whether the command has been killed.
state: the command's state. Can be one of
stopped: if the command was never startedstarted: if the command is currently runningerrored: if the command failed spawningexited: if the command is not running anymore, e.g. it received a close eventpid: the command's process ID.
stdin: a Writable stream to the command's stdin.
stdout: an RxJS observable to the command's stdout.
stderr: an RxJS observable to the command's stderr.
error: an RxJS observable to the command's error events (e.g. when it fails to spawn).
timer: an RxJS observable to the command's timing events (e.g. starting, stopping).
stateChange: an RxJS observable for changes to the command's state property.
messages: an object with the following properties:
incoming: an RxJS observable for the IPC messages received from the underlying process.outgoing: an RxJS observable for the IPC messages sent to the underlying process.Both observables emit MessageEvents.
Note that if the command wasn't spawned with IPC support, these won't emit any values.
close: an RxJS observable to the command's close events.
See CloseEvent for more information.
start(): starts the command and sets up all of the above streams
send(message[, handle, options]): sends a message to the underlying process via IPC channels,
returning a promise that resolves once the message has been sent.
See Node.js docs.
kill([signal]): kills the command, optionally specifying a signal (e.g. SIGTERM, SIGKILL, etc).
MessageEventAn object that represents a message that was received from/sent to the underlying command process.
It has the following properties:
message: the message itself.handle: a net.Socket,
net.Server or
dgram.Socket,
if one was sent, or undefined.CloseEventAn object with information about a command's closing event.
It contains the following properties:
command: a stripped down version of Command, including only name, command, env and cwd properties.index: the index of the command among all commands spawned.killed: whether the command exited because it was killed.exitCode: the exit code of the command's process, or the signal which it was killed with.timings: an object in the shape { startDate, endDate, durationSeconds }.Process exited with code null?
From Node child_process documentation, exit event:
This event is emitted after the child process ends. If the process terminated normally, code is the final exit code of the process, otherwise null. If the process terminated due to receipt of a signal, signal is the string name of the signal, otherwise null.
So null means the process didn't terminate normally. This will make concurrently
to return non-zero exit code too.
Does this work with the npm-replacements yarn, pnpm, or Bun?
Yes! In all examples above, you may replace "npm" with "yarn", "pnpm", or "bun".