gulpjs/gulp · error · Error
watching ${glob}: watch task has to be a function (optionall
Error message
watching ${glob}: watch task has to be a function (optionally generated by using gulp.parallel or gulp.series) What it means
This error is thrown by Gulp 4's gulp.watch() when the opt or task argument is a string or an array (checked at index.js:29-34). Gulp 3 let you pass task-name strings or arrays of task names to gulp.watch(); Gulp 4 removed that contract — the task argument must be a real function, optionally composed with gulp.parallel or gulp.series. The guard fires before delegation to the underlying watch() call so that invalid (legacy-style) task specifiers fail loudly instead of silently doing nothing.
Source
Thrown at index.js:31
this.task = this.task.bind(this);
this.series = this.series.bind(this);
this.parallel = this.parallel.bind(this);
this.registry = this.registry.bind(this);
this.tree = this.tree.bind(this);
this.lastRun = this.lastRun.bind(this);
this.src = this.src.bind(this);
this.dest = this.dest.bind(this);
this.symlink = this.symlink.bind(this);
}
util.inherits(Gulp, Undertaker);
Gulp.prototype.src = vfs.src;
Gulp.prototype.dest = vfs.dest;
Gulp.prototype.symlink = vfs.symlink;
Gulp.prototype.watch = function(glob, opt, task) {
if (typeof opt === 'string' || typeof task === 'string' ||
Array.isArray(opt) || Array.isArray(task)) {
throw new Error('watching ' + glob + ': watch task has to be ' +
'a function (optionally generated by using gulp.parallel ' +
'or gulp.series)');
}
if (typeof opt === 'function') {
task = opt;
opt = {};
}
opt = opt || {};
var fn;
if (typeof task === 'function') {
fn = this.parallel(task);
}
return watch(glob, opt, fn);
};View on GitHub (pinned to 61f22dc11b)
Solutions
- Replace string/array task names with function references composed via gulp.parallel() or gulp.series(), e.g. gulp.watch('src/**/*.js', gulp.parallel(lint, build)).
- If tasks are registered by name through gulp.task('name', fn), pass them through gulp.series('name') or gulp.parallel('name') — Undertaker resolves named strings inside these composers, but NOT as the raw task argument to watch().
- Check the installed version with npm ls gulp and confirm it is 4.x; align your gulpfile and any copied examples to the 4.x watch() API.
- Refactor named gulp.task() definitions into plain function declarations (function lint(){...}) and reference the function directly in watch(), which is the idiomatic Gulp 4 pattern.
Example fix
// before (gulp 3.x style — throws in gulp 4)
gulp.watch('src/**/*.js', ['lint', 'build']);
gulp.watch('src/**/*.js', 'build');
// after (gulp 4.x)
gulp.watch('src/**/*.js', gulp.parallel(lint, build));
gulp.watch('src/**/*.js', build);
// or, when tasks are registered by name:
gulp.watch('src/**/*.js', gulp.series('lint', 'build')); Defensive patterns
Strategy: validation
Validate before calling
function isWatchTaskValid(task) {
return task == null || typeof task === 'function';
}
// before calling watch:
if (typeof opt === 'string' || Array.isArray(opt) || typeof task === 'string' || Array.isArray(task)) {
throw new TypeError('gulp.watch: opt/task must be a function or options object, not a string/array');
}
gulp.watch(glob, opt, task); Type guard
/**
* Narrows a value to the gulp 4 watch() task contract:
* must be a function (or undefined). Rejects the gulp 3
* string/array task-name forms.
*/
function isGulpWatchTask(value) {
return typeof value === 'function';
}
// usage
if (isGulpWatchTask(myTask)) {
gulp.watch('src/**/*.js', myTask);
} else if (Array.isArray(myTask) || typeof myTask === 'string') {
// legacy gulp 3 form — convert with gulp.parallel/gulp.series
gulp.watch('src/**/*.js', gulp.parallel.apply(null, [].concat(myTask)));
} Try / catch
// Generally prefer validation over try/catch here — the error is a
// static API misuse, not a runtime/environment failure. If wrapping
// (e.g. a config loader that reads task names from disk):
try {
gulp.watch(glob, task);
} catch (err) {
if (/watch task has to be a function/.test(err.message)) {
console.error('gulp.watch: passed a task name/array but gulp 4 needs a function —',
'wrap with gulp.parallel() or gulp.series(). Got:', task);
// rethrow or fall back to a composed task built from names
gulp.watch(glob, gulp.series.apply(gulp, [].concat(task)));
} else {
throw err;
}
} Prevention
- Pin your gulp major version (gulp 3 vs 4) and read watch() docs/examples matching that exact version before writing gulpfile watch calls.
- Prefer function declarations (function lint(){...}) over gulp.task('lint', fn) so watch() always receives a function reference — no name-string ambiguity.
- Run a CI lint step that greps the gulpfile for gulp.watch(.*,\s*['\[] to catch legacy string/array task arguments before they ship.
- If migrating gulp 3 → 4, search the gulpfile for watch( calls and convert every string or array task argument to gulp.parallel()/gulp.series() in one pass.
- Keep a wrapper helper, e.g. function watchTasks(glob, tasks){ return gulp.watch(glob, gulp.parallel.apply(gulp, [].concat(tasks))); } to centralize the API contract.
When it happens
Trigger: Calling gulp.watch(glob, ['task1','task2']) (array as 2nd arg), gulp.watch(glob, 'task') (string as 2nd arg), gulp.watch(glob, options, 'task') (string as 3rd arg), or gulp.watch(glob, options, ['task1','task2']) (array as 3rd arg). Any value where typeof opt === 'string' || typeof task === 'string' || Array.isArray(opt) || Array.isArray(task) trips the throw at index.js:29-30.
Common situations: Migrating a gulpfile from gulp 3.x to 4.x without updating watch() calls; copying watch examples from a blog/tutorial written for gulp 3; mixing gulp.task('name', fn) named-task style with watch() by passing the name string; a version mismatch between locally installed gulp 4 and a globally installed gulp 3 CLI (or vice versa) producing inconsistent docs/API expectations.
AI-assisted analysis of gulpjs/gulp@61f22dc11b (2026-08-13).
Data as JSON: /api/errors/f019125e573b2c24.
Report an issue: GitHub.