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

  1. 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)).
  2. 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().
  3. 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.
  4. 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

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.