{"record":{"id":"7a818e9bf5847942","repo":"Automattic/mongoose","slug":"invalid-field-passed-to-sort","errorCode":null,"errorMessage":"Invalid field \"\" passed to sort()","messagePattern":"Invalid field \"\" passed to sort\\(\\)","errorType":"exception","errorClass":"MongooseError","httpStatus":null,"severity":"error","filePath":"lib/query.js","lineNumber":4767,"sourceCode":"  this._validateOp();\n  if (typeof op === 'string') {\n    this.op = op;\n  }\n\n  if (this.op == null) {\n    throw new MongooseError('Query must have `op` before executing');\n  }\n  if (this.model == null) {\n    throw new MongooseError('Query must have an associated model before executing');\n  }\n\n  const thunk = opToThunk.get(this.op);\n  if (!thunk) {\n    throw new MongooseError('Query has invalid `op`: \"' + this.op + '\"');\n  }\n\n  if (this.options?.sort && typeof this.options.sort === 'object' && Object.hasOwn(this.options.sort, '')) {\n    throw new MongooseError('Invalid field \"\" passed to sort()');\n  }\n\n  if (this._execCount > 0) {\n    let str = this.toString();\n    if (str.length > 60) {\n      str = str.slice(0, 60) + '...';\n    }\n    throw new MongooseError('Query was already executed: ' + str);\n  }\n  this._execCount++;\n\n  const _this = this;\n  return traceQuery(async function maybeTracedQueryExec() {\n    let skipWrappedFunction = null;\n    try {\n      await _this._hooks.execPre('exec', _this, []);\n    } catch (err) {\n      if (err instanceof Kareem.skipWrappedFunction) {","sourceCodeStart":4749,"sourceCodeEnd":4785,"githubUrl":"https://github.com/Automattic/mongoose/blob/49cdab01366679723b487ecb754b38570f783289/lib/query.js#L4749-L4785","documentation":"Before executing, Mongoose inspects query.options.sort and rejects an object that owns an empty-string key (sort({ '': 1 })). Sorting on an empty field name is meaningless and usually the result of building a sort object dynamically, so the check catches it client-side with a clear message.","triggerScenarios":"`query.sort({ '': 1 })`; `query.sort('')` or `query.sort('name ')` trimmed to an empty field; sort objects built as `{ [req.query.sortField]: 1 }` when sortField is '' or undefined-coerced; CSV/env-driven sort fields that arrive empty.","commonSituations":"Exposing a sort parameter from an HTTP query string or config without validating it; defaulting sort keys to '' instead of skipping; string parsing that splits on a delimiter and yields empty tokens.","solutions":["Validate/normalize user-supplied sort fields and skip empties: `if (field) sort[field] = dir;`.","Default to a real field or no sort at all when the input is empty.","Add allowlist checks so only known schema fields reach sort()."],"exampleFix":"// before\nconst sort = { [req.query.sortBy]: 1 }; // req.query.sortBy === '' → Invalid field \"\" passed to sort()\nawait Model.find().sort(sort).exec();\n\n// after\nconst allowed = ['name', 'createdAt', 'price'];\nconst sortBy = allowed.includes(req.query.sortBy) ? req.query.sortBy : 'createdAt';\nawait Model.find().sort({ [sortBy]: 1 }).exec();","handlingStrategy":"validation","validationCode":"function buildSort(sortBy, dir = 1, allowed = ['name','createdAt','price']) {\n  if (!sortBy || !allowed.includes(sortBy)) return {};\n  return { [sortBy]: dir };\n}","typeGuard":"const hasEmptySortKey = (sort) => sort != null && typeof sort === 'object' && Object.hasOwn(sort, '');","tryCatchPattern":"try { await Model.find().sort(sort).exec(); } catch (err) { if (err instanceof mongoose.Error && /Invalid field \"\"/.test(err.message)) { delete sort['']; return Model.find().sort(sort).exec(); } throw err; }","preventionTips":["Allowlist user-supplied sort fields against schema paths.","Skip empty sort keys instead of defaulting them to ''.","Trim and validate dynamic field names before building sort objects."],"tags":["mongoose","sort","query","user-input"],"backgroundTag":"empty-sort-field","analyzedSha":"49cdab01366679723b487ecb754b38570f783289","analyzedAt":"2026-08-21T22:54:00.882Z","schemaVersion":2},"datasetVersion":"2026-08-21T23:17:16.201Z"}