cheat sheet
concurrently
Package-level reference for concurrently on npm — install, output prefixing, kill-others, race-mode, and the npm-run-all2 comparison.
concurrently
What it is
concurrently runs multiple npm scripts in parallel and merges their output with optional name prefixes and colour. It's the most-used solution for "start the server AND the bundler watcher in one command", and it works cross-platform (bash and PowerShell both choke on & in different ways).
Originally written in 2015 by Kimmo Brunfeldt, it's stable, dependency-light, and ships an enormous battery of UX knobs: kill-others-on-fail, race-mode (first-to-finish wins), restart on exit, raw passthrough, custom prefix templates.
Install
# Almost always a dev dep
npm install -D concurrently
pnpm add -D concurrently
yarn add -D concurrently
# One-off
npx concurrently "npm:watch" "npm:server"
Output: concurrently (and shorthand concurrent, conc) binaries on PATH under node_modules/.bin/.
Versioning & Node support
- Current major line is
9.x— released late 2024. Drops Node 16 support, modernises the TS internals, refines--rawand--kill-otherssemantics. 8.x(2023) is widely deployed.- Recent releases require Node 18+.
- Always a dev dependency; never ships in runtime artefacts.
Package metadata
- Maintainer: Kimmo Brunfeldt (initial author), Gustavo Henke (current).
- Project home: github.com/open-cli-tools/concurrently
- npm: npmjs.com/package/concurrently
- License: MIT
- First released: 2015
- Downloads: ~10-15 million per week. Most JavaScript dev workflows have it in
devDependencieseven if they don't use it directly — it's a transitive dep of many scaffolds.
Peer dependencies & extras
concurrently has a handful of small dependencies (chalk, rxjs, tree-kill, yargs) and no peer deps. The bundle is ~50 KB minified. No common companion packages — it's a leaf utility.
| Adjacent tool | Purpose |
|---|---|
npm-run-all2 | Sequential / parallel script runner — script-glob support is its main differentiator |
tsx / ts-node | What you actually run through concurrently — TS execution |
nodemon | Auto-restart on file change — often paired with concurrently |
wait-on | Wait for a port/file/URL before continuing — pair with concurrently to start a server, wait, then run tests |
Alternatives
| Tool | Trade-off |
|---|---|
npm-run-all2 | Glob-based script matching (npm-run-all build:*), sequential by default with run-s / parallel with run-p. More terse for many-script chains. |
Bash & | npm run a & npm run b — works on Unix, breaks on Windows. Best fallback when concurrently is unwanted. |
make | Parallel via -j N; cross-platform if you ship make (POSIX-required in WSL/CI). |
turbo run | Monorepo-aware; runs across packages with topological awareness and remote caching. Overkill for single-package script orchestration. |
pnpm -r run | pnpm's built-in workspace script runner — sequential by default. |
Common gotchas
- Shell quoting hell.
concurrently "npm run a" "npm run b"works;concurrently npm run a npm run bdoes NOT (all four are positional args). Always quote each script. npm:prefix for named-prefix lookup.concurrently "npm:watch:*"matches every script starting withwatch:and uses the suffix as the prefix label. Withoutnpm:, it shells out the literal command.- Exit code defaults to the first failure with
--kill-others-on-fail, else 0. Bareconcurrentlyexits 0 even if one script crashed. Add--kill-others-on-fail(or--kill-others) for CI. --rawdisables prefix injection. Combined with--no-color, you get pristine subprocess output — useful when piping to a log aggregator that has its own structure.- Signals are forwarded best-effort. SIGINT propagates to children, but child cleanup is up to the child. nodemon, tsx, and webpack-dev-server all handle it; bespoke scripts may leak processes.
tree-killis used on Windows. Killing a child also kills its descendants. On macOS/Linux, default behaviour is the same but uses POSIX signal groups.- Output interleaving. Two scripts writing to stdout in parallel will interleave at line boundaries. Prefixes help; for fully separated streams use
--output-prefix+ a JSON logger per child.
Real-world recipes
Run multiple watchers
The canonical dev-server scenario: bundle, type-check, and serve in parallel.
// package.json
{
"scripts": {
"dev": "concurrently \"npm:dev:*\"",
"dev:server": "node --watch server.js",
"dev:client": "vite",
"dev:types": "tsc --watch --noEmit"
}
}
npm run dev
Output:
[dev:server] Server listening on http://localhost:3000
[dev:client] VITE v5.0.0 ready in 245 ms
[dev:client] ➜ Local: http://localhost:5173/
[dev:types] Found 0 errors. Watching for file changes.
Named output prefixes with colours
-n (or --names) names each script; -c (or --prefix-colors) colours them.
npx concurrently -n "API,WEB,TYPES" -c "blue,green,yellow" \
"npm run dev:server" "npm run dev:client" "npm run dev:types"
Output:
[API] listening on 3000
[WEB] ready in 245 ms
[TYPES] Found 0 errors.
For glob matches, prefixes come from the script name automatically:
npx concurrently "npm:dev:*"
# Prefixes: [server], [client], [types]
Output:
[server] api listening on 4000
[client] vite ready in 312 ms
[types] Found 0 errors.
Kill-others on first failure
By default, one script crashing doesn't stop the others. For CI / test:all aggregation, that's wrong — fail fast:
npx concurrently --kill-others-on-fail "npm:lint" "npm:typecheck" "npm:test"
Output:
[lint] ✓ no issues
[typecheck] ✗ error TS2304: Cannot find name 'foo'
--> Sending SIGTERM to other processes..
[test] EXITED via SIGTERM
Exit code is the first non-zero child's code, propagated. Use --kill-others (no -on-fail) if you also want a manual SIGINT to kill all peers.
Race mode — first script to finish wins
Useful for "one of these strategies should produce a result first":
npx concurrently --kill-others --success first \
"npm run fetch:from-cache" "npm run fetch:from-network"
Output:
[0] fetched from cache in 12 ms
--> Sending SIGTERM to other processes..
[1] EXITED via SIGTERM
--success first means concurrently exits 0 as soon as any one child exits 0; the rest are killed.
Wait-for-port pattern
Use wait-on (separate package) to wait for the server before running tests:
{
"scripts": {
"test:e2e": "concurrently -k -s first \"npm:dev\" \"npm:test:cypress\"",
"test:cypress": "wait-on http://localhost:3000 && cypress run"
}
}
-k (kill-others) + -s first (succeed on first exit) means: when Cypress finishes, kill the dev server and propagate Cypress's exit code.
Restart on exit
The --restart-tries N --restart-after MS flags re-launch a child that exits — handy for flaky watchers:
npx concurrently --restart-tries 3 --restart-after 1000 "npm:dev:server"
Output:
[0] server crashed: ECONNREFUSED
[0] restart attempt 1/3 in 1000ms
[0] server listening on 3000
Per-child env
concurrently doesn't have a built-in per-child env flag. Use shell prefixes:
{
"scripts": {
"dev": "concurrently \"PORT=3000 npm:dev:api\" \"PORT=5173 npm:dev:web\""
}
}
On Windows, use cross-env: concurrently \"cross-env PORT=3000 npm:dev:api\" ….
Production deployment
concurrently is a dev tool. Never ship it in dependencies — for production multi-process orchestration use:
pm2for Node process management- Docker Compose for multi-service local dev
- Kubernetes / Nomad for production fleets
- systemd for VM-based deployments
The one production-adjacent use: in a CI job that needs to run db migrate && (start-server & run-tests) cross-platform.
Performance tuning
concurrently has near-zero overhead — it spawns each child, multiplexes output via streams, and that's it. The only real knob:
--rawskips the prefix logic — minor speedup, more importantly preserves ANSI colour codes from child output for terminals that need them.--no-colorfor log files — strips concurrently's own colours; child colours still come through unless they detect non-TTY.
For very large numbers of parallel children (>20), prefer npm-run-all2 or a Makefile — concurrently's UX scales poorly past ~10 prefixes.
Version migration guide
| From → To | Highlights |
|---|---|
| 6 → 7 | New TypeScript internals. RxJS upgraded. Some --prefix template changes. |
| 7 → 8 | Node 14 dropped. tree-kill upgraded. Better Windows kill semantics. |
| 8 → 9 | Node 16 dropped. --kill-others-on-fail semantics clarified (was ambiguous in 8). |
Drop-in usage
Migration is usually painless — the CLI flags have been stable since v6. Read the changelog for the rare cases where --prefix templates changed.
Security considerations
- Children inherit env.
process.env(including secrets) is passed to every child. Strip withenv -iorcross-env --no-envif you need a clean child env. - Untrusted script args.
concurrently "$USER_INPUT"is shell injection. Never accept untrusted strings as concurrently arguments. --passthrough-args(v9+) forwards trailing CLI args to children. This is convenient but means a CI argument can affect every script — review usage.- No sandboxing. Children run with the same uid/gid as concurrently. For untrusted code use containers, not concurrently.
Configuration patterns
Via package.json
The dominant style. Everything is a script string:
{
"scripts": {
"dev": "concurrently -k -n WEB,API -c green,blue \"npm:dev:web\" \"npm:dev:api\"",
"dev:web": "vite",
"dev:api": "tsx watch src/server.ts"
}
}
Via JSON config file
Lesser-used; pass --config pointing at a JSON file. Useful when the command list is long enough to be unreadable inline:
// concurrently.json
{
"commands": [
{ "name": "web", "command": "npm run dev:web", "prefixColor": "green" },
{ "name": "api", "command": "npm run dev:api", "prefixColor": "blue" },
{ "name": "types", "command": "npm run dev:types", "prefixColor": "yellow" }
],
"killOthersOnFail": true
}
npx concurrently --config ./concurrently.json
Output:
[web] vite ready in 245 ms
[api] listening on 4000
[types] Found 0 errors.
Troubleshooting common errors
- "concurrently: command not found" — not installed, or
node_modules/.binnot on PATH.npx concurrently …works regardless. - Scripts run but no prefixes show —
--rawis set somewhere; or the child writes toprocess.stderronly, which concurrently passes through differently. - One child won't die on SIGINT — the child ignores or eats the signal.
concurrently --kill-othersusestree-kill(SIGKILL fallback after 1 s); set--kill-signal SIGTERMfor graceful shutdown if your child handles it. - Output interleaves at non-line boundaries — child is not line-buffering. Force line-buffering with
stdbuf -oL <cmd>on Linux/macOS. - "Exit code 0 even though one script failed" — missing
--kill-others-on-fail. Add it for CI use.
Ecosystem integrations
- CI configs — concurrently runs identically in GH Actions, GitLab CI, CircleCI, etc. No platform-specific quirks.
turbo run dev— Turborepo invokes per-package dev scripts in parallel; many of those scripts internally use concurrently for sub-processes.nx run-many --target=dev— Nx similarly. Sub-orchestration via concurrently is common.- Docker Compose — for multi-service dev, Docker Compose replaces concurrently entirely. Many teams keep concurrently for "fast local without Docker" and Compose for "full-stack with services".
When NOT to use this
- A single watcher. Just run the script directly — no orchestration needed.
- Production multi-process management. Use
pm2,forever, systemd, Docker Compose, or Kubernetes — concurrently has no health checks, no graceful restart, no log rotation. - Many script names matching a glob pattern.
npm-run-all2(run-p build:*) is more terse for that case. - Cross-package monorepo orchestration. Use Turborepo or Nx — they handle topological deps and remote caching that concurrently can't.
- You need health checks, port waiting, retries. Pair concurrently with
wait-on,wait-port, orpm2— concurrently has minimal lifecycle features.
concurrently shines in the "two-to-five watchers in dev mode" sweet spot. Outside that, reach for a more specialised tool.
See also
- Packages: npm-npm-run-all2 — sequential/parallel runner with globs
- Packages: npm-rimraf — companion in
clean && buildchains - Concept: pipes — process composition and stream multiplexing