{"record":{"id":"9f2d2fb2e70b5416","repo":"Automattic/mongoose","slug":"can-t-use-conditional-with-string","errorCode":null,"errorMessage":"Can't use ${conditional} with String.","messagePattern":"Can't use (.+?) with String\\.","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"lib/schema/string.js","lineNumber":694,"sourceCode":"Object.defineProperty(SchemaString.prototype, '$conditionalHandlers', {\n  enumerable: false,\n  value: $conditionalHandlers\n});\n\n/**\n * Casts contents for queries.\n *\n * @param {string} $conditional\n * @param {any} [val]\n * @api private\n */\n\nSchemaString.prototype.castForQuery = function($conditional, val, context) {\n  let handler;\n  if ($conditional != null) {\n    handler = this.$conditionalHandlers[$conditional];\n    if (!handler) {\n      throw new Error('Can\\'t use ' + $conditional + ' with String.');\n    }\n    return handler.call(this, val, context);\n  }\n\n  if (Object.prototype.toString.call(val) === '[object RegExp]' || isBsonType(val, 'BSONRegExp')) {\n    return val;\n  }\n\n  try {\n    return this.applySetters(val, context);\n  } catch (err) {\n    if (err instanceof CastError && err.path === this.path && this.$fullPath != null) {\n      err.path = this.$fullPath;\n    }\n    throw err;\n  }\n};\n","sourceCodeStart":676,"sourceCodeEnd":712,"githubUrl":"https://github.com/Automattic/mongoose/blob/49cdab01366679723b487ecb754b38570f783289/lib/schema/string.js#L676-L712","documentation":"When a query applies an operator to a String path, mongoose looks it up in SchemaString's `$conditionalHandlers`, which supports $eq, $gt, $gte, $in, $lt, $lte, $ne, $nin, $all, $exists, $type, $regex, $options, and $not. Any other operator (geospatial operators, $size, $mod, or a typo) has no handler and `castForQuery` throws during query building.","triggerScenarios":"`Model.find({ name: { $size: 3 } })` (string path); `Model.find({ name: { $near: { $geometry: ... } } })`; typo `{ name: { $regexx: /x/ } }`.","commonSituations":"Copy-pasting geospatial or array queries onto text fields; generic filter-builder UIs that attach any operator to any path; dynamic operator keys with spelling mistakes.","solutions":["Move the operator to a path of the matching type (e.g. $size on an array path, $near on a 2dsphere path)","Fix typos in operator keys, especially when built dynamically","Whitelist per-type operators in generic filter builders","Use $regex (or $eq) for string matching instead of unsupported operators"],"exampleFix":"// before\nModel.find({ name: { $size: 3 } }); // 'name' is String\n\n// after\nModel.find({ tags: { $size: 3 } }); // 'tags' is an array path","handlingStrategy":"validation","validationCode":"const STRING_PATH_OPS = new Set(['$eq','$gt','$gte','$in','$lt','$lte','$ne','$nin','$all','$exists','$type','$regex','$options','$not']);\nfunction assertStringOp(op) {\n  if (!STRING_PATH_OPS.has(op)) throw new Error(`operator ${op} is not supported on String paths`);\n}","typeGuard":"const isSupportedStringOp = op => STRING_PATH_OPS.has(op);","tryCatchPattern":"try {\n  await Model.find(filter);\n} catch (err) {\n  if (/^Can't use \\$(\\w+) with String\\.$/.test(err.message)) {\n    // operator unsupported on this path: reject filter or move it to the right path type\n  } else throw err;\n}","preventionTips":["Keep a per-type operator whitelist in generic filter builders","Run geospatial/array operators only on paths whose schema type supports them","Dry-run dynamic filters with a schema introspection pass before executing"],"tags":["mongoose","query","operators","string","castforquery"],"backgroundTag":"unsupported-query-operator","analyzedSha":"49cdab01366679723b487ecb754b38570f783289","analyzedAt":"2026-08-21T22:54:00.882Z","schemaVersion":2},"datasetVersion":"2026-08-21T23:17:16.201Z"}