webpack/webpack · error · Error

request should be a string or object with loader and options

Error message

request should be a string or object with loader and options (${JSON.stringify(value)})

What it means

Inside the vendored loader-runner, a LoaderObject's request setter accepts either a string (parsed into path/query/fragment) or a descriptor object. The descriptor path requires a `loader` string property (the loader's resolved path); the setter throws when value is an object lacking `loader`. The thrown message stringifies the bad value for diagnosis. This is hit when webpack/NormalModule resolves and assigns loader descriptors from module.rules.

Source

Thrown at lib/loaders/LoaderRunner.js:161

		return escapeHash(this.path) + escapeHash(this.query) + this.fragment;
	}

	/**
	 * @param {LoaderItemInput} value loader request or descriptor
	 */
	set request(value) {
		if (typeof value === "string") {
			const { path, query, fragment } = parseResource(value);
			this.path = path;
			this.query = query;
			this.fragment = fragment;
			this.options = undefined;
			this.ident = undefined;
			return;
		}

		if (!value.loader) {
			throw new Error(
				`request should be a string or object with loader and options (${JSON.stringify(
					value
				)})`
			);
		}

		const { loader: path, fragment, type, options, ident } = value;
		this.path = path;
		this.fragment = fragment || "";
		this.type = type;
		this.options = options;
		this.ident = ident;

		if (options === null || options === undefined) {
			this.query = "";
		} else if (typeof options === "string") {
			this.query = `?${options}`;
		} else if (ident) {

View on GitHub (pinned to 318421ea8a)

Solutions

  1. Inspect the offending rule/use entry and ensure each object has a `loader` string (e.g. { loader: 'babel-loader', options: {...} }).
  2. If using a function in module.rules.use, verify it returns { loader, options } and never an options-only object.
  3. Check any custom NormalModuleFactory tap that mutates or injects loader descriptors.

Example fix

// before
module.exports = {
  module: { rules: [{ test: /\.js$/, use: [{ options: { presets: ['@babel/env'] } }] }] }
};
// after
module.exports = {
  module: { rules: [{ test: /\.js$/, use: [{ loader: 'babel-loader', options: { presets: ['@babel/env'] } }] }] }
};
Defensive patterns

Strategy: type-guard

Validate before calling

// Validate module.rules.use entries before passing to webpack
function normalizeRules(rules) {
  for (const rule of rules) {
    const uses = Array.isArray(rule.use) ? rule.use : (rule.use ? [rule.use] : []);
    for (const u of uses) {
      if (u && typeof u === 'object' && !('loader' in u) && !u.loader) {
        throw new Error('Rule use entry is missing a "loader" field: ' + JSON.stringify(u));
      }
    }
  }
}

Type guard

/** @typedef {{ loader: string, options?: object }} LoaderDescriptor */
/** @param {unknown} u
 * @returns {u is LoaderDescriptor} */
function isLoaderDescriptor(u) {
  return !!u && typeof u === 'object' && typeof u.loader === 'string';
}

Prevention

When it happens

Trigger: A rule's `use`/`loader` entry resolves to an object without a `loader` key, e.g. { options: {...} } with no loader, or a malformed loader descriptor returned by a custom resolve hook. Also reachable if a plugin taps NormalModuleFactory and emits a bad loader descriptor.

Common situations: Typos in module.rules (writing { options: {} } instead of { loader: 'babel-loader', options: {} }). A plugin or custom resolver that constructs loader descriptors dynamically and forgets the loader field. Corrupted/empty loader string after path resolution.

Related errors


AI-assisted analysis of webpack/webpack@318421ea8a (2026-08-03). Data as JSON: /data/errors/a05f55d8bf18e048.json. Report an issue: GitHub.