Before you read a property on a plain object, it helps to know whether that key actually exists—as an own property, on the prototype chain, or not at all—because reading a missing key quietly yields undefined and hides typos. This guide compares seven practical approaches (Object.hasOwn, in, hasOwnProperty, Object.keys, Object.getOwnPropertyNames, Object.getOwnPropertySymbols, and Reflect.ownKeys) so you can match the API to your intent. When you only care about enumerable string keys for serialization-style work, pairing Object.keys with ideas from looping object keys in JavaScript is also common.
Tested on: Node.js v20.18.2. Short notes after each snippet describe the values you should see in the console.
Quick reference
Use this table when you need javascript check if object has key or js check if key exists at a glance.
| Need | Use |
|---|---|
| Own string/symbol key (recommended default) | Object.hasOwn(obj, key) |
| Own or inherited key | key in obj |
| Only enumerable own string keys | Object.keys(obj).includes(key) (or loop) |
| All own string keys (incl. non-enumerable) | Object.getOwnPropertyNames(obj).includes(key) |
| Own symbol keys | Object.getOwnPropertySymbols(obj).includes(sym) |
| Everything own (strings + symbols, any enumerability) | Reflect.ownKeys(obj).includes(keyOrSymbol) |
1. Object.hasOwn (modern default for “javascript object has key”)
Object.hasOwn(obj, prop) (ES2022) asks whether prop is an own property of obj—not inherited. MDN recommends it over calling obj.hasOwnProperty(...) on unknown objects.
const car = { make: "Toyota", model: "Corolla" };
console.log(Object.hasOwn(car, "make"));
console.log(Object.hasOwn(car, "year"));You should see true then false—make is own, year is absent.
Inherited keys are excluded, unlike in:
console.log("toString" in {});
console.log(Object.hasOwn({}, "toString"));in reports true (inherited toString), while Object.hasOwn reports false for an empty object’s own keys.
2. The in operator (own or prototype chain)
prop in obj is true if prop exists on obj or its prototype chain—useful when inheritance matters, awkward for plain “dictionary” objects where inherited names like toString can surprise you.
const book = { title: "1984", author: "George Orwell" };
console.log("title" in book, "author" in book, "published" in book);You should see three booleans: true, true, then false for the missing published key.
Nested object: in still checks one object at a time.
const company = {
name: "Innovatech",
department: { name: "Research", head: "Alice Johnson" },
};
console.log("name" in company, "head" in company.department, "budget" in company.department);Expect true for name on company, true for head on department, and false for budget (not defined on that nested object).
Prototype example (tested): if the key lives on the prototype, in is true but Object.hasOwn is false.
const withProto = Object.create({ budget: 999 });
withProto.name = "X";
console.log("budget" in withProto, Object.hasOwn(withProto, "budget"));You should see true then false: budget exists via the prototype, but it is not an own property of withProto.
3. hasOwnProperty (legacy instance method)
obj.hasOwnProperty("key") matches Object.hasOwn for ordinary objects, but an object can override hasOwnProperty as a data field—so libraries often use Object.prototype.hasOwnProperty.call(obj, "key"). Prefer Object.hasOwn when available.
const university = {
name: "State University",
faculty: {
arts: { head: "Dr. Emily Rose" },
science: { head: "Dr. John Doe" },
},
};
console.log(
university.hasOwnProperty("name"),
university.hasOwnProperty("faculty"),
university.faculty.hasOwnProperty("arts"),
university.faculty.arts.hasOwnProperty("head"),
university.faculty.hasOwnProperty("budget"),
);The log prints five booleans ending in false only for the non-existent faculty.budget key.
4. Object.keys + includes (enumerable string keys only)
Object.keys(obj) returns only own enumerable string keys. It does not list symbols or non-enumerable own keys.
const person = { name: "Sarah", age: 25 };
console.log(Object.keys(person).includes("name"));
console.log(Object.keys(person).includes("occupation"));You should see true for name and false for occupation.
Nested:
const product = { id: 101, details: { price: 29.99, stock: 120 } };
console.log(Object.keys(product.details).includes("price"));
console.log(Object.keys(product.details).includes("discount"));Expect true for price and false for discount.
5. Object.getOwnPropertyNames + includes (all string own keys)
Object.getOwnPropertyNames(obj) lists all own string property names, enumerable or not—useful when a key was defined with enumerable: false.
const vehicle = { make: "Honda" };
Object.defineProperty(vehicle, "hiddenFeature", {
value: "secret",
enumerable: false,
});
console.log(Object.getOwnPropertyNames(vehicle).includes("make"));
console.log(Object.getOwnPropertyNames(vehicle).includes("hiddenFeature"));
console.log(Object.keys(vehicle).includes("hiddenFeature"));Three lines: true for make, true for the non-enumerable hiddenFeature via getOwnPropertyNames, and false when using Object.keys (non-enumerable keys are omitted).
Nested:
const company = { name: "Tech Corp", department: { name: "Development" } };
Object.defineProperty(company.department, "internalCode", {
value: "X123",
enumerable: false,
});
console.log(
Object.getOwnPropertyNames(company.department).includes("name"),
Object.getOwnPropertyNames(company.department).includes("internalCode"),
);Both checks print true—name is enumerable; internalCode is still listed by getOwnPropertyNames.
6. Object.getOwnPropertySymbols + includes
For symbol-keyed own properties:
const user = { name: "John", [Symbol("password")]: "12345" };
const symbols = Object.getOwnPropertySymbols(user);
const pwdSym = symbols.find((s) => s.description === "password");
console.log(symbols.includes(pwdSym));You should see true once the located symbol is found in the symbol list.
Nested:
const settings = { level: 1, options: { [Symbol("secret")]: "hidden_value" } };
const optionSymbols = Object.getOwnPropertySymbols(settings.options);
const secSym = optionSymbols.find((s) => s.description === "secret");
console.log(optionSymbols.includes(secSym));Again true when the matching symbol reference is in the array returned for the nested object.
7. Reflect.ownKeys + includes (strings and symbols, any enumerability)
Reflect.ownKeys(obj) returns all own keys—strings and symbols—regardless of enumerability. It still does not walk the prototype chain (unlike in).
const company = { name: "Tech Solutions", department: { employees: 250 } };
Object.defineProperty(company.department, "budget", {
value: 1_000_000,
enumerable: false,
});
const departmentSymbols = Symbol("internalCode");
company.department[departmentSymbols] = "Dept01";
const departmentKeys = Reflect.ownKeys(company.department);
console.log(
departmentKeys.includes("employees"),
departmentKeys.includes("budget"),
departmentKeys.includes(departmentSymbols),
);One line with three true values: enumerable employees, non-enumerable budget, and the symbol key are all “own” per Reflect.ownKeys.
Edge cases and related scenarios
The seven methods above cover plain objects, but real code runs into special cases where the obvious check gives a misleading result. This section collects the gotchas and related data structures you are most likely to hit—keys whose value is undefined, Map and Set collections, nested lookups, batch checks, and arrays—so the right tool is clear when an object check is not enough.
undefined still means “key exists” for own properties
A common mistake is testing obj.key === undefined to decide if a key is missing. But a key can exist and hold the value undefined, so a value check cannot tell “absent” from “present but undefined.” The existence methods report the key correctly:
const o = { a: undefined };
console.log("a" in o, Object.hasOwn(o, "a"), Object.keys(o).includes("a"));All three checks print true even though the value stored at a is undefined.
Map / Set — use has, not object key rules
A Map or Set is not a plain object, and in / Object.hasOwn do not inspect their entries. Each collection ships a purpose-built has() method—Map.has(key) for keys and Set.has(value) for membership—which is the correct (and faster) way to test presence:
const m = new Map([["id", 1]]);
const s = new Set(["x"]);
console.log(m.has("id"), m.has("missing"), s.has("x"), s.has("y"));You should see true, false, true, false in order—Map.has / Set.has mirror membership without touching object-key helpers.
Optional chaining for nested values (not a key-exists API)
Optional chaining (?.) safely reads down a nested path without throwing a TypeError on a missing level, but it checks values, not keys—a present key holding undefined and a missing key both yield undefined:
const company = { department: { name: "R&D" } };
console.log(String(company?.department?.budget));The expression stringifies to "undefined" (the string), because the missing path yields the value undefined.
Combine it with in / Object.hasOwn on the final object when you must distinguish missing vs undefined.
Check several keys at once
When you need to confirm that all required keys are present—for example validating a config object—pair Array.prototype.every() with Object.hasOwn to fold the per-key checks into a single boolean:
const cfg = { host: "db", port: 5432 };
console.log(["host", "port"].every((k) => Object.hasOwn(cfg, k)));
console.log(["host", "ssl"].every((k) => Object.hasOwn(cfg, k)));The first every is true (both keys exist); the second is false because ssl is missing.
Arrays: in checks indices, not values
Arrays are objects whose keys are numeric indices, so in tests whether an index exists, never whether a value is in the array. Use includes() to search by value and reserve in for “does this position exist”:
const fruits = ["Apple", "Banana"];
console.log(0 in fruits, 2 in fruits);Index 0 exists (true); index 2 does not (false)—this is not the same as searching for "Apple" in values.
Summary
- Use
Object.hasOwnfor the common “js check if object has key” case: own properties only, safe and clear. - Use
inwhen inheritance should count; remember prototype pollution surprises on plain objects. - Use
Object.keysonly when enumerable string keys are what you mean; usegetOwnPropertyNames/Reflect.ownKeyswhen non-enumerable or symbol keys matter. Reflect.ownKeys,Object.keys, andgetOwnPropertyNamesall enumerate own keys of the target object—they do not replaceinfor inherited properties.
References
MDN reference pages for each API used in the seven patterns above.
- MDN:
Object.hasOwn() - MDN:
inoperator - MDN:
Object.prototype.hasOwnProperty() - MDN:
Object.keys() - MDN:
Object.getOwnPropertyNames() - MDN:
Object.getOwnPropertySymbols() - MDN:
Reflect.ownKeys()

