Use Object.hasOwn(obj, key). It returns true only when the object itself has that key, whatever value the key holds, even undefined. Use key in obj when inherited keys should count too. Don't use obj.key !== undefined: a key that exists with the value undefined reads as missing.
Reading a key an object doesn't have never throws in JavaScript. You just get undefined, the same value you get from a key that exists and holds undefined. So reading the value can't answer "is this key here?", and the language gives you two dedicated checks instead: Object.hasOwn for the object's own keys and in for own plus inherited. Below are both, the undefined trap, the older hasOwnProperty, Map.has, and nested keys with optional chaining. Each example runs on this page: hit Run, then edit the code and run it again.
1Object.hasOwnRecommended
Object.hasOwn(obj, key) (ES2022) asks whether the object has the key as its own property, meaning one set on the object itself rather than inherited from its prototype. For plain data like parsed JSON, config and records, that is the question you mean. It is a static function, so it works on every object, including ones with no prototype.
Output
Prints true, false, true, false, true, true. The last line is the one people get wrong with other checks: debug holds false, and hasOwn still says the key is there, because it never looks at the value. To search values instead of keys, use Object.values(obj).includes(value), which scans every entry.
2The in operator
key in obj is the other built-in check. The difference from hasOwn is that in walks the prototype chain, so it also finds inherited keys. On a plain object that means every method from Object.prototype, such as toString, counts as present.
Output
"toString" in config prints true for an object that never defined it, and the class instance prints true false for greet: methods live on User.prototype, not on ada. So pick by intent. Use hasOwn for keys in data, and in for "can I call or read this on the object?", which is also how you feature-check an object's capabilities. in needs an object on the right; "a" in "abc" throws a TypeError.
3The trap: obj.key !== undefined
The most common home-made check is obj.key !== undefined (or its older form, typeof obj.key !== "undefined"). It tests the value, not the key, so it can't tell a missing key from a key set to undefined. A plain if (obj.key) is worse: every falsy value, including 0 and "", reads as missing.
Output
Read the timeout row: exists: true but !== undefined: false, so the value check reports a key that is really there as missing, exactly like the missing row. The truthy column is false for all five rows. If you only care whether there is a usable value, !== undefined is fine. If you need to know the key exists, for example to tell "explicitly set to undefined" apart from "never set", use hasOwn or in.
4hasOwnProperty in older code
Before ES2022 the standard answer was obj.hasOwnProperty(key). It gives the same result as Object.hasOwn on ordinary objects, but it is a method the object inherits, so it breaks whenever the object doesn't have it: objects made with Object.create(null), or data that has its own key called hasOwnProperty.
Output
The method call fails with TypeError: dict.hasOwnProperty is not a function, while Object.hasOwn and the Object.prototype.hasOwnProperty.call(obj, key) workaround both print true. That workaround is what linters (ESLint's no-prototype-builtins) used to require. Object.hasOwn is the same thing with a shorter name, and it is available in every current browser and Node version, so new code has no reason to use the method form.
5Map.has for Map keys
If the data is a Map rather than a plain object, the check is built in: map.has(key). A Map has no prototype keys to trip over, and its keys can be any value, not just strings. The same undefined trap applies to map.get(key), so use has for existence.
Output
get prints undefined undefined for the missing ftp and the stored legacy, while has("legacy") prints true. The last two lines show the other difference: the Map keeps 1 and "1" apart (true false), but a plain object converts keys to strings, so both checks print true true. Reach for a Map when keys come from user input or aren't strings.
6Which should you use?
| Check | Inherited keys | Key set to undefined | Best for |
|---|---|---|---|
| Object.hasOwn(obj, key) | Ignored | Found | Keys in plain data (the default) |
| key in obj | Included | Found | Class instances, capability checks |
| map.has(key) | None to worry about | Found | Map objects, non-string keys |
| obj.hasOwnProperty(key) | Ignored | Found | Older code; breaks on null-prototype objects |
| obj.key !== undefined | Included | Reported missing | Checking for a usable value, not a key |
7Nested keys with optional chaining
For a key several levels down, the problem is the levels in between: reading a property of undefined throws. Optional chaining, a?.b?.c, stops at the first null or undefined and returns undefined instead of throwing. Pair it with ?? for a default, or with hasOwn when you need to know that the final key exists.
Output
The plain read fails with TypeError: Cannot read properties of undefined (reading 'size'); the ?. versions print 0, undefined, undefined. Note ?? 5432 keeps the real port 0, while || 5432 replaces it with 5432, the falsy trap from section 3 again. Optional chaining shares the undefined limit too: data.db?.port can't tell a missing port from one set to undefined, which is why the last two lines hand the parent (or an empty-object fallback) to hasOwn and in.
Frequently asked questions
What is the difference between `Object.hasOwn` and the `in` operator?
Object.hasOwn(obj, key) only looks at the object’s own keys. key in obj also looks up the prototype chain, so "toString" in {} is true and a class method is found on its instances. For keys in plain data use Object.hasOwn; use in when inherited properties should count.
Should I use `Object.hasOwn` or `hasOwnProperty`?
Use Object.hasOwn. It returns the same answer as obj.hasOwnProperty(key) on ordinary objects, but it also works on objects created with Object.create(null) and on objects that have their own key named hasOwnProperty, where the method form throws or misbehaves. It is ES2022 and available in all current browsers and Node versions. In code that has to run on older engines, use Object.prototype.hasOwnProperty.call(obj, key).
Why is `obj.key !== undefined` not a reliable way to check for a key?
Because it checks the value, not the key. A key that exists with the value undefined looks exactly like a missing key, and a missing key that is inherited from the prototype looks present. typeof obj.key === "undefined" has the same problem. It is fine when you only care whether there is a usable value; for existence use Object.hasOwn or in.
How do I check if a nested key exists in JavaScript?
Use optional chaining to reach the parent without throwing, then check the last key: Object.hasOwn(data.db ?? {}, "port"). If you just want the value with a default, data.db?.port ?? 5432 is enough. Use ?? rather than || so real falsy values such as 0 or "" are kept.
How do I check if an object has a certain value?
Search the values instead of the keys: Object.values(obj).includes(value). Unlike a key check it scans every entry, so if you do it often, build a reverse lookup (a Map or object from value to key) once and check that.
Does `Object.keys(obj).includes(key)` work?
It gives the same answer as Object.hasOwn for ordinary string keys, but it builds an array of every key and then scans it, so it does more work for no benefit. It also skips non-enumerable and symbol keys. Use Object.hasOwn(obj, key).
Run it yourself
Open any of these in the full JavaScript editor: tweak, run, and share.
JavaScript playground