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: all examples in this tutorial were run with Node.js v20.18.2, and the output shown below each snippet is its exact console output.
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"));true
falseInherited keys are excluded, unlike in:
console.log("toString" in {});
console.log(Object.hasOwn({}, "toString"));true
false2. 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);true true falseNested 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);true true falsePrototype 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"));true false3. 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"),
);true true true true false4. 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"));true
falseNested:
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"));true
false5. 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"));true
true
falseNested:
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"),
);true true6. 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));trueNested:
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));true7. 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),
);true true trueEdge 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"));true true trueMap / 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"));true false true falseOptional 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));undefinedCombine 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)));true
falseArrays: 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);true falseSummary
- 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()

