{"record":{"id":"7b010d15677f7ca4","repo":"Automattic/mongoose","slug":"cast-to-text-failed-for-value-value-type","errorCode":null,"errorMessage":"Cast to $text failed for value \"${value}\" (type ${valueType}) at path \"${path}\"","messagePattern":"Cast to \\$text failed for value \"(.+?)\" \\(type (.+?)\\) at path \"(.+?)\"","errorType":"validation","errorClass":"CastError","httpStatus":null,"severity":"error","filePath":"lib/schema/operators/text.js","lineNumber":20,"sourceCode":"\nconst CastError = require('../../error/cast');\nconst castBoolean = require('../../cast/boolean');\nconst castString = require('../../cast/string');\n\n/**\n * Casts val to an object suitable for `$text`. Throws an error if the object\n * can't be casted.\n *\n * @param {any} val value to cast\n * @param {string} [path] path to associate with any errors that occurred\n * @return {object} casted object\n * @see https://www.mongodb.com/docs/manual/reference/operator/query/text/\n * @api private\n */\n\nmodule.exports = function castTextSearch(val, path) {\n  if (val == null || typeof val !== 'object') {\n    throw new CastError('$text', val, path);\n  }\n\n  if (val.$search != null) {\n    val.$search = castString(val.$search, path + '.$search');\n  }\n  if (val.$language != null) {\n    val.$language = castString(val.$language, path + '.$language');\n  }\n  if (val.$caseSensitive != null) {\n    val.$caseSensitive = castBoolean(val.$caseSensitive,\n      path + '.$caseSensitive');\n  }\n  if (val.$diacriticSensitive != null) {\n    val.$diacriticSensitive = castBoolean(val.$diacriticSensitive,\n      path + '.$diacriticSensitive');\n  }\n\n  return val;","sourceCodeStart":2,"sourceCodeEnd":38,"githubUrl":"https://github.com/Automattic/mongoose/blob/49cdab01366679723b487ecb754b38570f783289/lib/schema/operators/text.js#L2-L38","documentation":"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.","triggerScenarios":"`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.","commonSituations":"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.","solutions":["Always use the object form: `Model.find({ $text: { $search: term } })`","Default-construct the object when the term may be absent: `const q = term ? { $text: { $search: term } } : {}`","Ensure a text index exists (`schema.index({ body: 'text' })`) — without it a valid $text query fails server-side with a different error","Validate search input is a non-empty string before composing the filter"],"exampleFix":"// before\nconst results = await Model.find({ $text: req.query.q }); // bare string\n\n// after\nconst q = String(req.query.q ?? '').trim();\nif (!q) return [];\nconst results = await Model.find({ $text: { $search: q } });","handlingStrategy":"validation","validationCode":"function toTextFilter(term) {\n  const q = String(term ?? '').trim();\n  if (!q) return {}; // no text search\n  return { $text: { $search: q } }; // always object form\n}","typeGuard":"function isTextFilter(v) {\n  return v != null && typeof v === 'object' && typeof v.$search === 'string';\n}","tryCatchPattern":"try { await Model.find({ $text: value }); } catch (err) { if (err.name === 'CastError' && err.kind === '$text') { return badRequest('$text requires { $search: string }'); } throw err; }","preventionTips":["Always wrap the term: { $search: term }","Skip $text entirely for empty search strings instead of passing ''","Create the text index before shipping $text endpoints"],"tags":["mongoose","text-search","full-text","query","cast"],"backgroundTag":"mongoose-cast-error","analyzedSha":"49cdab01366679723b487ecb754b38570f783289","analyzedAt":"2026-08-21T22:54:00.882Z","schemaVersion":2},"datasetVersion":"2026-08-21T23:17:16.201Z"}