Object.getOwnPropertyDescriptor()

The inspection half of the descriptor system. Its plural form is the missing piece for copying an object faithfully, because Object.assign flattens getters into values.

Object static methodES5 (2009)Live demo
Common call
Object.getOwnPropertyDescriptor(o, "a")
Returns
a descriptor object, or undefined
Replaces
guessing what flags a property has
Watch out
undefined for INHERITED properties, not just missing ones
Object.getOwnPropertyDescriptor(objectobject — The object to inspect. Primitives are coerced; null and undefined throw.type: object · required, keykey — The property key. Only own properties are considered — the prototype chain is not searched.type: string | symbol · required)
→ object | undefined

Demo

Live evaluation
Try:
Inputs
jsonstringa JSON object
keystringproperty name
Output
Object.getOwnPropertyDescriptor(JSON.parse('{"a":1}'), 'a')
{ value: 1, writable: true, enumerable: true, configurable: true }

A property created by a literal or by JSON parsing comes back with all three flags true — writable, enumerable, configurable — which is what plain assignment always produces. The third case shows that inherited properties give undefined: toString exists on the prototype, not on this object, so there is no OWN descriptor to report. The last case is worth looking at: an array length is writable but NOT enumerable and NOT configurable, which is exactly why it never appears in Object.keys and cannot be deleted.

Parameters

NameTypeRequiredDescription
objectobjectyesThe object to inspect. Primitives are coerced; null and undefined throw.
keystring | symbolyesThe property key. Only own properties are considered — the prototype chain is not searched.

Return value

object | undefined — A descriptor object — value and writable for a data property, get and set for an accessor, plus enumerable and configurable. undefined if the property is not an OWN property.

Common patterns

Copy an object faithfully
The idiom Object.assign cannot manage.
Object.create(
  Object.getPrototypeOf(src),
  Object.getOwnPropertyDescriptors(src)
);
Check whether a property is writable
Before attempting an assignment that might throw.
const d = Object.getOwnPropertyDescriptor(o, k);
if (d?.writable) o[k] = v;
Detect a getter
An accessor descriptor has get instead of value.
const isAccessor = "get" in (Object.getOwnPropertyDescriptor(o, k) ?? {});

Examples

1. A literal property
Object.getOwnPropertyDescriptor({a: 1}, "a")
Returns
{value: 1, writable: true, enumerable: true, configurable: true}
2. Missing
Object.getOwnPropertyDescriptor({}, "x")
Returns
undefined
3. Inherited too
Object.getOwnPropertyDescriptor({}, "toString")
Returns
undefined
4. A getter
Object.getOwnPropertyDescriptor({get a() { return 1; }}, "a")
Returns
{get: f, set: undefined, enumerable: true, configurable: true}
5. Array length
Object.getOwnPropertyDescriptor([1], "length")
Returns
{value: 1, writable: true, enumerable: false, configurable: false}
6. All at once
Object.keys(Object.getOwnPropertyDescriptors({a: 1, b: 2}))
Returns
['a', 'b']

Pitfalls

1. undefined means "not an own property", not "no such property"
Inherited properties return undefined exactly as missing ones do. A method on a class prototype, or anything from Object.prototype, has no own descriptor on the instance — so this cannot be used as an existence check.
Looks absent
Object.getOwnPropertyDescriptor({}, "toString")
undefined
Ask the right question
"toString" in {}
true
2. Object.assign loses getters; this is the fix
assign READS each source property, so a getter is invoked once and its result stored as a static value. Copying descriptors instead preserves the getter as a getter — the difference between a live computed property and a stale snapshot.
Frozen snapshot
const s = {get now() { return Date.now(); }};
Object.assign({}, s).now
a fixed number
Descriptors survive
Object.defineProperties({}, Object.getOwnPropertyDescriptors(s))
still a getter
3. The returned descriptor is a copy
Mutating it changes nothing on the original object. To apply a modified descriptor you have to pass it back through defineProperty — which will fail if the property was non-configurable.
No effect
const d = Object.getOwnPropertyDescriptor(o, "a");
d.value = 99;
o.a
unchanged
Write it back
Object.defineProperty(o, "a", d)
applied
4. It does not see symbol keys unless you ask for them
The singular form takes any key including a symbol, but if you are enumerating, remember getOwnPropertyNames omits symbols. getOwnPropertyDescriptors includes them, which is why it is the correct basis for a faithful copy.
Symbols missed
Object.getOwnPropertyNames({[Symbol("s")]: 1})
[]
Descriptors include them
Object.getOwnPropertySymbols({[Symbol("s")]: 1}).length
1

When to use

Use it
  • Copying an object with getters, setters and flags preserved
  • Inspecting why a property is invisible or read-only
  • Writing generic code that must respect property attributes
  • Debugging an object that does not behave as its literal suggests
Reach for something else
  • You only want the value → read the property
  • You only want to know it exists → Object.hasOwn or in
  • A shallow merge is enough → Object.assign or spread
  • You want to list keys → Object.keys or getOwnPropertyNames

Notes

Complexity
O(1) for one property; O(n) for the plural form
Return
A new descriptor object each call — mutating it does nothing
CPython impl
V8: Builtins-object-getownpropertydescriptor
Memory
Allocates one descriptor object per property
Thread-safe
Single-threaded; the object is only read

FAQ

Combine the prototype with every descriptor. This preserves getters, setters, non-enumerable properties and symbol keys — all of which Object.assign and spread silently discard or flatten.

const copy = Object.create(
  Object.getPrototypeOf(src),
  Object.getOwnPropertyDescriptors(src)
);

History

ES5
getOwnPropertyDescriptor added with the descriptor model.
ES2017
getOwnPropertyDescriptors added specifically to make faithful object copying possible.