145

I am doing MongoDB lookups by converting a string to BSON. Is there a way for me to determine if the string I have is a valid ObjectID for Mongo before doing the conversion?

Here is the coffeescript for my current findByID function. It works great, but I'd like to lookup by a different attribute if I determine the string is not an ID.

db.collection "pages", (err, collection) ->
  collection.findOne
    _id: new BSON.ObjectID(id)
  , (err, item) ->
    if item
      res.send item
    else
      res.send 404
Will
  • 2,082
  • 2
  • 16
  • 13

22 Answers22

238

I found that the mongoose ObjectId validator works to validate valid objectIds but I found a few cases where invalid ids were considered valid. (eg: any 12 characters long string)

var ObjectId = require('mongoose').Types.ObjectId;
ObjectId.isValid('microsoft123'); //true
ObjectId.isValid('timtomtamted'); //true
ObjectId.isValid('551137c2f9e1fac808a5f572'); //true

What has been working for me is casting a string to an objectId and then checking that the original string matches the string value of the objectId.

new ObjectId('timtamtomted'); //616273656e6365576f726b73
new ObjectId('537eed02ed345b2e039652d2') //537eed02ed345b2e039652d2

This work because valid ids do not change when casted to an ObjectId but a string that gets a false valid will change when casted to an objectId.

Akarsh Satija
  • 1,756
  • 2
  • 22
  • 28
Andy Macleod
  • 2,889
  • 1
  • 13
  • 16
  • Theoretically you could add these two methods to generate a pretty damn good ObjectID validator, will get that done today. – Anthony Mar 17 '16 at 17:38
  • 3
    So, something like this? ```function checkObjectIdValid(id){ if(ObjectID.isValid(id)){ if(new ObjectID(id) === id){ return true } else { return false } } else { return false } }``` – Jackson Vaughan May 17 '19 at 12:01
  • Something like that would work, or string comparison using ObjetcId’s toString function. – Andy Macleod May 17 '19 at 12:09
  • But if you got bad input which is not 12 length and contains special keys? You will get an error by the time you will do `ObjectId(INPUT)`. I think it better to wrap the validation using try and catch function. – Raz Buchnik Jun 23 '19 at 07:51
  • 7
    Actually @JacksonVaughan answer is almost right. It was missing a String() to convert the new ObjectID(id) to a string since we are comparing it with another string. Here is the complete right answer: `const ObjectId = require('mongoose').Types.ObjectId; function isObjectIdValid(id) { if (ObjectId.isValid(id)) { if (String(new ObjectId(id)) === id) { return true } else { return false } } else { return false } }` – marcvander Feb 15 '20 at 12:19
  • 3
    @marcvander let me `es6`ify that for you: `isObjectIdValid = id => ObjectId.isValid(id) ? String(new ObjectId(id) === id) ? true : false : false;` – Rod911 May 09 '20 at 16:09
  • 3
    Standard function exists now (isValidObjectId), check my answer here - https://stackoverflow.com/a/61779949/11896312 – think-serious May 13 '20 at 16:34
  • Explanation to [why 12 characters can be correct id](https://stackoverflow.com/questions/58279842/how-is-a-mongodb-objectid-12-bytes). – superpupervlad May 08 '22 at 14:56
  • @marcvander why use `String()` when the resulting ObjectId instance has a `toString()` method? – Uche Ozoemena May 19 '22 at 13:54
  • Let me add my function too with native MongoDB driver, as the others didn't work for me: `try { return id.toString() === new ObjectId(id).toString(); } catch (err) { return false; }` – m.spyratos Apr 30 '23 at 17:56
109

✅ Build In Solution isValidObjectId() > Mongoose 5.7.12

If you are using Mongoose, we can test whether a String is of 12 bytes or a string of 24 hex characters by using mongoose build-in isValidObjectId.

This is SUFFICIENT to perform a valid query without throwing any invalid object id errors

mongoose.isValidObjectId(string); /* will return true/false */

DO NOTE!

isValidObjectId() is most commonly used to test a expected objectID, in order to avoid mongoose throwing invalid object ID error.

Example

if (mongoose.isValidObjectId("some 12 byte string")) {
     return collection.findOne({ _id: "some 12 byte string" })
     // returns null if no record found.
}

If you do not conditionally test whether expected objectID is valid, you will need to catch the error.

try {
  return collection.findOne({ _id: "abc" }) 
  //this will throw error
} catch(error) {
  console.log('invalid _id error', error)
}

Since findOne({ _id: null }) and findOne({ _id: undefined }) are completely valid queries (doesn't throw error), isValidObjectId(undefined) and isValidObjectId(null) will return true.

DO NOTE 2!

123456789012 may not appear to look like a bson string but it's completely a valid ObjectID because the following query does not throw error. (return null if no record found).

findOne({ _id: ObjectId('123456789012')}) // ✅ valid query

313233343536373839303132 may appear to look like a 24 character string (it's the hex value of 123456789012), but it's also a valid ObjectId because the following query does not throw error. (return null if no record found)

findOne({ _id: ObjectId('313233343536373839303132')}) // ✅ valid query

The following are invalid (1 string char less than above examples)

findOne({ _id: ObjectId('12345678901')}) // ❌ not 12 byte string
findOne({ _id: ObjectId('31323334353637383930313')}) // ❌ not 24 char hex

Format of ObjectId

ObjectIds are small, likely unique, fast to generate, and ordered. ObjectId values are 12 bytes in length, consisting of:

  • a 4-byte timestamp value, representing the ObjectId's creation, measured in seconds since the Unix epoch
  • a 5-byte random value generated once per process. This random value is unique to the machine and process.
  • a 3-byte incrementing counter, initialized to a random value

Due to the above random value, ObjectId cannot be calculated. It can only appear to be a 12 byte string, or 24 character hex string.

Someone Special
  • 12,479
  • 7
  • 45
  • 76
  • 2
    seriously, let's vote this thing up. The old accepted answer was great when the library provided no support for this, but now it does. To bypass it is a hack that depends on implementation details. – lance.dolan Dec 18 '20 at 22:33
  • This is accepting any random number of character length 12 as a valid object id. Is there any way to check only for object IDs with 24 characters that too in an accepted format i.e. alphanumeric? By accepted format this is what is meant like described here https://docs.mongodb.com/manual/reference/method/ObjectId/ – Avani Khabiya Aug 12 '21 at 06:06
  • notice there is a random 5 byte value in the objectId. More often than not, `isValidObjectId` is more than sufficient since your queries will not be rejected by mongoose. If it's a invalid ObjectId, mongoose will throw error if your schema requires objectid. Checking for validObjectID is to reject the query before your mongoose throw the error. – Someone Special Aug 12 '21 at 09:12
  • take into account that this function returns true if the input is undefined. This may not be what you want. – Mihai Nov 23 '21 at 16:40
  • Yes and No. `undefined` can be treated as a valid ObjectID simply because if you do `Model.find({ _id: undefined})`, it will not throw an error. While if you do `Model.find({ _id: 'abc'})` (invalid ObjectID), it will throw a **Cast to ObjectId failed** error – Someone Special Nov 23 '21 at 20:21
  • This string - how-fit-am-i - is being returned as true. Can anyone explain why that might be? – trurohit Nov 24 '21 at 13:30
  • it’s is valid because error will not be thrown by your mongoose validation should you use ObjectId as the field type. It will simply return null if no results are found. If you were to use a shorter/longer string, your query will throw error which need to be caught by your try/catch block. – Someone Special Nov 25 '21 at 10:09
  • Sadly it validates `undefined` and `null` – ImportError Jan 18 '22 at 06:10
  • this is correct behavior, because `findOne({ _id: undefined})` should not throw an error. undefined and null are valid values. You may refer to https://github.com/Automattic/mongoose/blob/a8a615cf89ffe65d3145aaeb1d64df8887eb105d/lib/index.js#L957 where it intentionally returns true on null – Someone Special Jan 18 '22 at 07:07
  • Still returns true for stuff like `microsoft123` and `Manajemenfs2` – Tushar Shahi Jan 25 '22 at 12:03
  • It's true because it's a string of 12 characters and `findOne({_id: ObjectId('microsoft123')})` will return null (if record does not exist) instead of throwing error. – Someone Special Jan 25 '22 at 13:30
  • added explanation on why it's true in answer. – Someone Special Jan 25 '22 at 13:52
103

You can use a regular expression to test for that:

CoffeeScript

if id.match /^[0-9a-fA-F]{24}$/
    # it's an ObjectID
else
    # nope

JavaScript

if (id.match(/^[0-9a-fA-F]{24}$/)) {
    // it's an ObjectID    
} else {
    // nope    
}
JohnnyHK
  • 305,182
  • 66
  • 621
  • 471
  • 1
    Hmm this could match non-objectIds as well, best way is to either build a validator based on the spec and regex it's specific parts or try to make a new objectid and house a catch block to catch if it can do it. – Sammaye Dec 13 '12 at 08:20
  • 2
    @Sammaye It's the same validation that's used by the [BSON ObjectID constructor](https://github.com/mongodb/js-bson/blob/master/lib/bson/objectid.js#L15). Can you give me an example of a non-ObjectID string it would match? – JohnnyHK Dec 13 '12 at 13:34
  • Wow, I didn't see that coming. Well any 24 character string that has numbers and letters in, i.e. `lol456712bbfghLLsdfr` – Sammaye Dec 13 '12 at 13:37
  • @Sammaye The regex only matches alpha chars a-f and A-F so that string wouldn't match. – JohnnyHK Dec 13 '12 at 13:42
  • Well ok maybe I wrote that a little fast so: `456546576541232123adcdeA` would then match – Sammaye Dec 13 '12 at 14:02
  • 16
    @Sammaye But that's a valid ObjectID, so it should match. – JohnnyHK Dec 13 '12 at 14:16
  • see comments [here](http://stackoverflow.com/a/11989159/645458) regarding the same – gmajivu Jan 07 '14 at 20:08
  • 2
    Probably the right way, officially suggested by mongoose https://github.com/Automattic/mongoose/issues/1959#issuecomment-97457325 – Akarsh Satija Dec 26 '16 at 10:21
  • This should be the accepted answer, this is what is officially suggested by the folks at mongoose. – Anthony Aug 21 '17 at 17:55
20

I have used the native node mongodb driver to do this in the past. The isValid method checks that the value is a valid BSON ObjectId. See the documentation here.

var ObjectID = require('mongodb').ObjectID;
console.log( ObjectID.isValid(12345) );
cbaigorri
  • 2,467
  • 25
  • 26
  • doesn't seem to work, above returns true for a random number. – Dan Ochiana Mar 08 '17 at 12:50
  • 3
    I think that's most likely because it should be `ObjectId`, not `ObjectID`. :) – Ken Hoff Mar 20 '19 at 18:45
  • 2
    this should be accepted answer, no need for mongoose – F.H. Nov 22 '20 at 16:48
  • I think this answer is quite old... The only thing I could find in the latest version is this one: [https://mongodb.github.io/node-mongodb-native/5.3/classes/BSON.ObjectId.html#isValid](https://mongodb.github.io/node-mongodb-native/5.3/classes/BSON.ObjectId.html#isValid), but I get the same issue as @DanOchiana, passing it a number number it returns `true` – m.spyratos Apr 30 '23 at 17:20
6

mongoose.Types.ObjectId.isValid(string) always returns True if string contains 12 letters

let firstUserID = '5b360fdea392d731829ded18';
let secondUserID = 'aaaaaaaaaaaa';

console.log(mongoose.Types.ObjectId.isValid(firstUserID)); // true
console.log(mongoose.Types.ObjectId.isValid(secondUserID)); // true

let checkForValidMongoDbID = new RegExp("^[0-9a-fA-F]{24}$");
console.log(checkForValidMongoDbID.test(firstUserID)); // true
console.log(checkForValidMongoDbID.test(secondUserID)); // false
Sajag Porwal
  • 71
  • 1
  • 4
4

The simplest way to check if the string is a valid Mongo ObjectId is using mongodb module.

const ObjectID = require('mongodb').ObjectID;

if(ObjectID.isValid(777777777777777)){
   console.log("Valid ObjectID")
}
Pavneet Kaur
  • 729
  • 7
  • 5
  • It's nuts how popular Mongoose is. The core mongodb driver is more powerful and in newer versions with POJO support Mongoose is just a layer of inefficiency. – Robert Mar 11 '22 at 22:33
  • Yes. In fact, native mongo query functions are way more faster than using mongoose functions. – Pavneet Kaur Mar 12 '22 at 12:23
3

Below is a function that both checks with the ObjectId isValid method and whether or not new ObjectId(id) returns the same value. The reason for isValid not being enough alone is described very well by Andy Macleod in the chosen answer.

const ObjectId = require('mongoose').Types.ObjectId;

/**
 * True if provided object ID valid
 * @param {string} id 
 */
function isObjectIdValid(id){ 
  return ObjectId.isValid(id) && new ObjectId(id) == id;
}
AliAvci
  • 1,127
  • 10
  • 20
3

Mongoose 6.2.5 introduces mongoose.isObjectIdOrHexString() which returns true only if the given value is an ObjectId instance or a 24 character hex string representing an ObjectId, and will return false for numbers, documents, and strings of length 12 (unlike mongoose.isValidObjectId() which is just a wrapper for mongoose.Types.ObjectId.isValid() in Mongoose 6)

Mongoose.prototype.isObjectIdOrHexString()

Parameters

  • v «Any»

Returns true if the given value is a Mongoose ObjectId (using instanceof) or if the given value is a 24 character hex string, which is the most commonly used string representation of an ObjectId.

This function is similar to isValidObjectId(), but considerably more strict, because isValidObjectId() will return true for any value that Mongoose can convert to an ObjectId. That includes Mongoose documents, any string of length 12, and any number. isObjectIdOrHexString() returns true only for ObjectId instances or 24 character hex strings, and will return false for numbers, documents, and strings of length 12.

Example:

mongoose.isObjectIdOrHexString(new mongoose.Types.ObjectId()); // true
mongoose.isObjectIdOrHexString('62261a65d66c6be0a63c051f'); // true

mongoose.isObjectIdOrHexString('0123456789ab'); // false
mongoose.isObjectIdOrHexString(6); // false
mongoose.isObjectIdOrHexString(new User({ name: 'test' })); // false
mongoose.isObjectIdOrHexString({ test: 42 }); // false
Fraction
  • 11,668
  • 5
  • 28
  • 48
2

Here is some code I have written based on @andy-macleod's answer.

It can take either an int or string or ObjectId and returns a valid ObjectId if the passed value is valid or null if it is invalid:

var ObjectId= require('mongoose').Types.ObjectId;

function toObjectId(id) {

    var stringId = id.toString().toLowerCase();

    if (!ObjectId.isValid(stringId)) {
        return null;
    }

    var result = new ObjectId(stringId);
    if (result.toString() != stringId) {
        return null;
    }

    return result;
}
nzjoel
  • 1,106
  • 9
  • 17
2

The only way i found is to create a new ObjectId with the value i want to check, if the input is equal to the output, the id is valid :

function validate(id) {
    var valid = false;
    try
    {
        if(id == new mongoose.Types.ObjectId(""+id))
           valid = true;

    }
    catch(e)
    {
       valid = false;
    }
    return valid;
}

> validate(null)
false
> validate(20)
false
> validate("abcdef")
false
> validate("5ad72b594c897c7c38b2bf71")
true
Daphoque
  • 4,421
  • 1
  • 20
  • 31
1

The easiest way is basically wrap your ObjectId method in a try and catch service. Then you are using this service to handle Objecet Id's, instead of using the method directly:

var ObjectId = REQUIRE OR IMPORT ...

// service
function oid(str) {
 try {   
   return ObjectId(str);
 } catch(err) {
   return false;
 }
}

// usage
if (oid(USER_INPUT)) {
  // continue
} else {
  // throw error
}

You can also send null or empty props to get a new generated ID.

Raz Buchnik
  • 7,753
  • 14
  • 53
  • 96
1

@ross-u answer is just amazing.

I have chained the methods to do a full validation inline:

documentId = id && isValid(id) && new ObjectId(id) == id ? id : null

Note the double equal sign, which is VERY important as new ObjectId() does not return a string and strict comparison will return false when compared against a normal string (which I had coming in my logic).

The methods have been destructured from the mongoose object exposed by the require:

const {
  Types: {
    ObjectId: { isValid },
    ObjectId
  }
} = require("mongoose");
Mattia Rasulo
  • 1,236
  • 10
  • 15
1

This approach may help somebody. It works with nodejs mongodb driver

if (ObjectId.isValid(stringId) && (ObjectId(stringId).toString() === stringId)){
  // your operation
}
Ramesh Murugesan
  • 4,727
  • 7
  • 42
  • 67
0

If you have the hex string you can use this:

ObjectId.isValid(ObjectId.createFromHexString(hexId));
pkarc
  • 417
  • 5
  • 9
0

It took me a while to get a valid solution as the one proposed by @Andy Macleod of comparing objectId value with its own string was crashing the Express.js server on:

var view_task_id_temp=new mongodb.ObjectID("invalid_id_string"); //this crashed

I just used a simple try catch to solve this.

var mongodb = require('mongodb');
var id_error=false;
try{
    var x=new mongodb.ObjectID("57d9a8b310b45a383a74df93");
    console.log("x="+JSON.stringify(x));
}catch(err){
    console.log("error="+err);
    id_error=true;
}

if(id_error==false){
   // Do stuff here
}
Vibhu Tewary
  • 282
  • 2
  • 11
0

For mongoose , Use isValid() function to check if objectId is valid or not

Example :

var ObjectId = mongoose.Types.ObjectId;
if(ObjectId.isValid(req.params.documentId)){
   console.log('Object id is valid'); 
}else{
   console.log('Invalid Object id');
}
Om Prakash Sharma
  • 1,682
  • 22
  • 13
  • 1
    This solution will evaluate to true the strings that begin with a hex digit and have a right length, such as `"comfort12345"`. – urosc Jan 20 '22 at 09:50
0

Adding to the accepted answer of Andy Macleod, I created a helper function that can be used to check both strings and ObjectId's.

Implentation:

var ObjectId = require("mongoose").Types.ObjectId;


function isValidObjectId(value) {
  // If value is an ObjectId cast it to a string to allow
  // passing string or ObjectId as an argument.
  var valueString = typeof value === "string" ? value : String(value); 
  
  // Cast the string to ObjectId
  var idInstance = new ObjectId(valueString); 

  return String(idInstance) === valueString;
}

Explanation:

In the accepted answer Andy Macleod said:

What has been working for me is casting a string to an objectId and then checking that the original string matches the string value of the objectId.


With Invalid ObjectId string

Casting an invalid string (like "microsoft") to ObjectId, gives a completely different value:

        "microsoft"
            ↓
String( new ObjectId("microsoft")  );
            ↓
"6d6963726f736f6674313233"
            ↓
"microsoft" === "6d6963726f736f6674313233" // false

With Valid ObjectId string

Casting a valid string like ("6d6963726f736f6674313233") to ObjectId, gives the same value:

"6d6963726f736f6674313233"
            ↓
String( new ObjectId("6d6963726f736f6674313233") )
            ↓
"6d6963726f736f6674313233"     
            ↓
"6d6963726f736f6674313233" === "6d6963726f736f6674313233"  // true
urosc
  • 1,938
  • 3
  • 24
  • 33
0

https://mongodb.github.io/node-mongodb-native/api-bson-generated/objectid.html#objectid-isvalid

From MongoDB documentation:

Constructor documentation:

ObjectID()
Constructor
Create a new ObjectID instance

class ObjectID()
    Arguments:  
        id (string) – Can be a 24 byte hex string, 12 byte binary string or a Number.
    Returns:    
        object instance of ObjectID.

ObjectId.isValid() documentation:

Checks if a value is a valid bson ObjectId

ObjectID.isValid()
    Returns:    boolean return true if the value is a valid bson ObjectId, return false otherwise.

So, isValid(...) will return true if argument is a valid BSON ObjectId object, and the constructor ObjectId(...) only accepts a valid BSON ObjectId object argument (Basically, isValid(...) will return false if an exception will be thrown by constructor).

Knowing this. We can first check if argument is a valid ObjectId, and then create an ObjectId with that value and compare HEX strings.

const objectId = 'str_object_id';
const isValid = ObjectId.isValid(objectId) && new ObjectId(objectId).toHexString() == objectId;

If passed objectId is not a valid ObjectId HEX string (it has not been converted by constructor), this method will return false.

0

If you don't want to use a function exported by a mongo db driver or ORM, you can do some simple objectId validation using this RegExp:

/[0-9a-f]{24}/i

Check this thread for more information.

Allan Juan
  • 2,048
  • 1
  • 18
  • 42
0

For plain javscript, this is helpful:

const isMongoId = str => {
    return str.match(/^[0-9a-fA-F]{24}$/);
  };
olawalejuwonm
  • 1,315
  • 11
  • 17
0

Solution without mongoose

import {ObjectId} from 'bson';

ObjectId.isValid(value);
Daniel
  • 7,684
  • 7
  • 52
  • 76
-3

Warning: isValid will return true for arbitrary 12/24 length strings beginning with a valid hex digit. Currently I think this is a better check:

((thing.length === 24 || thing.length === 12) && isNaN(parseInt(thing,16)) !== true)

iss42
  • 2,720
  • 3
  • 21
  • 37
  • 4
    This would evaluate to `true` for `'funky string'`. Any string that's the right length and starts with a hex digit will satisfy it. – JohnnyHK Dec 24 '14 at 00:30