Automattic/mongoose · error · CastError

Cast to $text failed for value "${value}" (type ${valueType}

Error message

Cast to $text failed for value "${value}" (type ${valueType}) at path "${path}"

What it means

castTextSearch handles the top-level `$text` filter key (lib/cast.js routes `filter.$text` here) and throws CastError '$text' when the value is null or not an object. $text requires an object form — `{ $search: 'term', $language: 'en', $caseSensitive: false, $diacriticSensitive: false }` — and each present sub-option is then cast to its type. Passing the search string bare ('term') or forgetting the object wrapper triggers this before the query ever reaches MongoDB.

Source

Thrown at lib/schema/operators/text.js:20

const CastError = require('../../error/cast');
const castBoolean = require('../../cast/boolean');
const castString = require('../../cast/string');

/**
 * Casts val to an object suitable for `$text`. Throws an error if the object
 * can't be casted.
 *
 * @param {any} val value to cast
 * @param {string} [path] path to associate with any errors that occurred
 * @return {object} casted object
 * @see https://www.mongodb.com/docs/manual/reference/operator/query/text/
 * @api private
 */

module.exports = function castTextSearch(val, path) {
  if (val == null || typeof val !== 'object') {
    throw new CastError('$text', val, path);
  }

  if (val.$search != null) {
    val.$search = castString(val.$search, path + '.$search');
  }
  if (val.$language != null) {
    val.$language = castString(val.$language, path + '.$language');
  }
  if (val.$caseSensitive != null) {
    val.$caseSensitive = castBoolean(val.$caseSensitive,
      path + '.$caseSensitive');
  }
  if (val.$diacriticSensitive != null) {
    val.$diacriticSensitive = castBoolean(val.$diacriticSensitive,
      path + '.$diacriticSensitive');
  }

  return val;

View on GitHub (pinned to 49cdab0136)

Solutions

  1. Always use the object form: `Model.find({ $text: { $search: term } })`
  2. Default-construct the object when the term may be absent: `const q = term ? { $text: { $search: term } } : {}`
  3. Ensure a text index exists (`schema.index({ body: 'text' })`) — without it a valid $text query fails server-side with a different error
  4. Validate search input is a non-empty string before composing the filter

Example fix

// before
const results = await Model.find({ $text: req.query.q }); // bare string

// after
const q = String(req.query.q ?? '').trim();
if (!q) return [];
const results = await Model.find({ $text: { $search: q } });
Defensive patterns

Strategy: validation

Validate before calling

function toTextFilter(term) {
  const q = String(term ?? '').trim();
  if (!q) return {}; // no text search
  return { $text: { $search: q } }; // always object form
}

Type guard

function isTextFilter(v) {
  return v != null && typeof v === 'object' && typeof v.$search === 'string';
}

Try / catch

try { await Model.find({ $text: value }); } catch (err) { if (err.name === 'CastError' && err.kind === '$text') { return badRequest('$text requires { $search: string }'); } throw err; }

Prevention

When it happens

Trigger: `Model.find({ $text: 'hello' })` (bare string), `Model.find({ $text: null })`, or `{ $text: { $search: 123 } }` (inner options then fail their own casts via castString/castBoolean on `path.$search` etc.). Note $text is a top-level filter key, never placed under a field path.

Common situations: Wrapping a search term directly instead of `{ $search: ... }`; query-builder code that short-circuits empty searches to the raw string; missing text index is a *different* (server-side) error — this one is purely shape validation on the filter.

Related errors


AI-assisted analysis of Automattic/mongoose@49cdab0136 (2026-08-21). Data as JSON: /api/errors/7b010d15677f7ca4. Report an issue: GitHub.