929 words
5 minutes

JavaScript Property Exists but Is undefined: Use Object.hasOwn

2026-10-27
Frontend
JavaScript
/
Node.js
/
Troubleshooting
Contents

Use Object.hasOwn(record, key) when you need to know whether a JavaScript object contains its own property. An own property set to undefined still exists. Reading record[key] !== undefined cannot distinguish that property from a missing key.

The opposite mistake is accepting too much: key in record also accepts inherited properties. Choose the question first—own property, inherited-or-own property, or usable value—then choose the predicate.

Reproduce the difference with one record#

The following fixture was executed on macOS with Node.js 24.13.0 during article preparation on October 27, 2026. It compares the same keys with five checks, then exercises two failures of calling record.hasOwnProperty() directly. Save it as presence-check.mjs and run node presence-check.mjs.

The ECMAScript Object.hasOwn algorithm checks ownership after converting the key to a property key. The fixture uses string and symbol keys on ordinary objects; it does not rely on truthiness to decide membership.

import assert from 'node:assert/strict';
const record = Object.create({ inherited: 7 });
Object.assign(record, { unset: undefined, zero: 0, disabled: false, empty: '', nil: null });
Object.defineProperty(record, 'hidden', { value: 9 });
const token = Symbol('token');
record[token] = 11;
const cases = [
['absent', 'absent', [false, false, false, false, false]],
['undefined', 'unset', [true, true, false, false, true]],
['zero', 'zero', [true, true, false, true, true]],
['false', 'disabled', [true, true, false, true, true]],
['empty string', 'empty', [true, true, false, true, true]],
['null', 'nil', [true, true, false, true, true]],
['inherited', 'inherited', [false, true, true, true, false]],
['non-enumerable', 'hidden', [true, true, true, true, false]],
['symbol', token, [true, true, true, true, false]],
];
for (const [label, key, expected] of cases) {
const actual = [Object.hasOwn(record, key), key in record,
Boolean(record[key]), record[key] !== undefined,
Object.keys(record).includes(key)];
assert.deepEqual(actual, expected, label);
console.log(`${label}: ${actual.map(Number).join(' ')}`);
}
const dictionary = Object.create(null);
dictionary.name = 'Ada';
assert.throws(() => dictionary.hasOwnProperty('name'), TypeError);
assert.equal(Object.hasOwn(dictionary, 'name'), true);
const shadowed = { hasOwnProperty: false, name: 'Ada' };
assert.throws(() => shadowed.hasOwnProperty('name'), TypeError);
assert.equal(Object.hasOwn(shadowed, 'name'), true);
assert.equal(Object.prototype.hasOwnProperty.call(shadowed, 'name'), true);
assert.throws(() => Object.hasOwn(null, 'name'), TypeError);
assert.throws(() => Object.hasOwn(undefined, 'name'), TypeError);
let reads = 0;
const accessor = { get name() { reads++; return 'Ada'; } };
assert.equal(Object.hasOwn(accessor, 'name'), true);
assert.equal(reads, 0);
assert.equal(accessor.name, 'Ada');
assert.equal(reads, 1);
console.log('dictionary, shadowing, nullish, and getter checks passed');

Expected output uses 1 for true and 0 for false. Columns are own, in, truthy, not undefined, and in Object.keys, in that order:

absent: 0 0 0 0 0
undefined: 1 1 0 0 1
zero: 1 1 0 1 1
false: 1 1 0 1 1
empty string: 1 1 0 1 1
null: 1 1 0 1 1
inherited: 0 1 1 1 0
non-enumerable: 1 1 1 1 0
symbol: 1 1 1 1 0
dictionary, shadowing, nullish, and getter checks passed

The inherited row is deliberately truthy. It exposes a bug that an undefined-only example would miss: reading a useful-looking value does not prove the object owns it. The own undefined row exposes the reverse error: no useful value does not mean no property.

Match the check to the question#

MDN’s ownership guide separates ownership, enumerability, and key type. Those are independent properties, which is why the last two rows differ from a normal enumerable string key.

Your questionCheckBoundary shown by the fixture
Does this object own the key?Object.hasOwn(record, key)Includes own undefined, hidden, and symbol properties
Can the key be found on the object or its prototype chain?key in recordAccepts the inherited key too
Does reading the key produce a truthy value?Boolean(record[key])Rejects zero, false, empty string, null, and undefined
Does reading the key produce something other than undefined?record[key] !== undefinedAccepts inherited values and rejects own undefined
Is this an enumerable own string key?Object.keys(record).includes(key)Excludes the hidden property and symbol

Do not replace a field’s validation with an ownership test. For example, Object.hasOwn(record, 'nil') is true here, but the value is null. If a field must contain a number, check both presence and the accepted numeric range. Conversely, if false or zero is allowed, a truthiness test would reject valid input.

Use in when inherited properties intentionally count. It is appropriate for that different question; switching every in expression to Object.hasOwn() would change the contract.

Avoid calling a method supplied by the record#

MDN recommends Object.hasOwn over a direct hasOwnProperty() call. In the fixture, a null-prototype dictionary has no inherited method, while the second record supplies a boolean with that method’s name. Both direct calls throw TypeError; the static method succeeds.

If your target runtime lacks Object.hasOwn, Object.prototype.hasOwnProperty.call(record, key) is the older borrowed-method form. Its result is asserted for the shadowed record above. The published execution covers Node.js 24.13.0, not an older runtime or a browser compatibility matrix; check your actual target before adopting the static API.

The null-prototype dictionary is an object. A null or undefined receiver is a different input: Object.hasOwn(null, key) and Object.hasOwn(undefined, key) throw, as the assertions confirm. Validate an optional receiver before making the call. Optional chaining on a value read would suppress that access error but still would not answer the ownership question.

Keep presence separate from reading and transport#

The getter counter demonstrates another useful boundary. On the ordinary accessor object in the fixture, Object.hasOwn(accessor, 'name') reports presence without running the getter. Reading accessor.name then increments the counter. These checks do not establish behavior for proxies, framework reactive wrappers, custom key coercion, or objects that mutate during inspection.

Presence can also change across serialization. An own undefined object field disappears from ordinary JSON output; see the separate JSON.stringify undefined-field diagnosis before assuming an in-memory membership check describes a received request body.

For a record-membership bug, add an assertion for an own undefined property and one for an inherited property. If the first must pass and the second must fail, Object.hasOwn() expresses that requirement directly. Assert the value separately when its type or contents matter.

References:

JavaScript Property Exists but Is undefined: Use Object.hasOwn
https://laplusda.com/en/posts/javascript-property-exists-undefined-hasown/
Author
Zero
Published at
2026-10-27
License
CC BY-NC-SA 4.0