Object.getOwnPropertyDescriptor()

ES5+

Returns the full descriptor of an own property — its value or get/set functions plus the writable, enumerable, and configurable flags — or undefined when the object has no such own property. It is the inspection counterpart to Object.defineProperty().

Syntax

Object.getOwnPropertyDescriptor(obj, prop)

Parameters

obj Object

The object to look for the property on

prop string

The name of the property whose descriptor should be retrieved

Return Value

Object | undefined

A property descriptor of the given property, or undefined if it does not exist

Examples

JavaScript
const obj = { name: 'John' };
const desc = Object.getOwnPropertyDescriptor(obj, 'name');
console.log(desc.value);
console.log(desc.writable);
Output:
// 'John' true

📌 When to Use

Use Object.getOwnPropertyDescriptor() when you need to know how a property is defined, not just what it holds. Frequent scenarios: debugging why an assignment silently does nothing (the descriptor reveals writable: false) or why a key is missing from Object.keys() and JSON output (enumerable: false); distinguishing data properties from accessors before copying, because generic copying flattens getters into snapshot values; building faithful cloning and mixin utilities that migrate accessors and flags via Object.getOwnPropertyDescriptors() combined with defineProperties or Object.create(); and writing tests that assert API surface details, such as a library keeping a property read-only or a polyfill installing itself non-enumerably. Decorator and framework code inspects descriptors to wrap existing getters and setters with logging, memoization, or access control while preserving the original behavior underneath. Remember it reports own properties only — to find where an inherited property is actually defined, walk upward with Object.getPrototypeOf() and query each level until the descriptor turns up.

⚠️ Common Mistakes

Reading desc.value for accessor properties - descriptors are either data descriptors (value, writable) or accessor descriptors (get, set), never both, so an accessor's descriptor has value: undefined. Branch on "get" in desc before deciding which fields to trust.

Assuming the descriptor stays live - the returned object is a snapshot. Editing desc.writable changes nothing until you feed the modified descriptor back through Object.defineProperty(), a two-step round trip that is easy to leave half-finished.

Expecting it to return descriptors for inherited properties - it only works on own properties, not inherited ones.

Not checking for undefined return value - if the property does not exist, it returns undefined, not an error.

Forgetting that Symbol properties also have descriptors - use getOwnPropertyDescriptor with Symbol keys for complete inspection.

✅ Best Practices

When auditing class instances, inspect both levels: query the instance for own descriptors, then Object.getPrototypeOf(obj) for the accessors that class syntax places on the prototype - a getter defined in a class body will not show up on the instance itself, which is a regular source of "descriptor is undefined" confusion. A small helper that walks the chain and returns the first matching descriptor plus its owner answers the question definitively.

Wrap existing accessors instead of clobbering them: fetch the descriptor, keep references to the original get and set functions, and redefine the property with wrappers that call through - the standard technique for adding logging or memoization to properties you do not own.

Diagnose property weirdness with a one-liner before reaching for a debugger: console.log(Object.getOwnPropertyDescriptor(obj, "key")) answers "why can I not write this?" and "why is this missing from keys()?" in a single glance at the flags.

Use Object.getOwnPropertyDescriptors() (plural) to get all property descriptors at once for efficient copying.

Combine with Object.defineProperties() for accurate property copying: Object.defineProperties({}, Object.getOwnPropertyDescriptors(source))

Create debugging utilities that display all property attributes in a readable format.

⚡ Performance Notes

A single descriptor lookup is O(1) and allocates one small result object — negligible everywhere. The plural Object.getOwnPropertyDescriptors() gathers all own descriptors in one native pass and is markedly cheaper than looping getOwnPropertyDescriptor over Object.getOwnPropertyNames(), as well as being the only correct bulk approach when Symbol-keyed properties matter. Descriptor-based cloning (Object.create with getOwnPropertyDescriptors) does more work per property than spread because every flag is validated and applied, so reserve it for objects that genuinely carry accessors or non-default flags; plain data objects clone faster with spread or structuredClone(). Inspection utilities that dump descriptors for whole object graphs belong in devtools and tests rather than production hot paths, purely because of the per-property allocation churn they generate.

🌍 Real World Example

Complete Object Cloning Utility

Two utilities here rely on descriptor inspection. inspectProperty() renders a property's full metadata — data versus accessor, flags, getter and setter presence — into a readable report, the output you want when debugging why serialization skips a field. cloneWithDescriptors() then performs a faithful copy: by feeding Object.getOwnPropertyDescriptors() into Object.create() with the source's prototype, the clone keeps its working value accessor pair and its non-enumerable _value backing field, both of which a spread copy would silently destroy. The final Object.keys() output confirms the preserved enumerability.

function inspectProperty(obj, prop) {
  const desc = Object.getOwnPropertyDescriptor(obj, prop);
  if (!desc) return null;

  return {
    property: prop,
    type: 'get' in desc ? 'accessor' : 'data',
    value: desc.value,
    hasGetter: !!desc.get,
    hasSetter: !!desc.set,
    writable: desc.writable,
    enumerable: desc.enumerable,
    configurable: desc.configurable
  };
}

function cloneWithDescriptors(source) {
  // Get the prototype
  const proto = Object.getPrototypeOf(source);

  // Create new object with same prototype and all property descriptors
  return Object.create(
    proto,
    Object.getOwnPropertyDescriptors(source)
  );
}

// Example: Object with getter/setter
const original = {
  _value: 0,
  get value() { return this._value; },
  set value(v) { this._value = v; }
};

// Make _value non-enumerable
Object.defineProperty(original, '_value', { enumerable: false });

// Inspect properties
console.log(inspectProperty(original, 'value'));
// { property: 'value', type: 'accessor', hasGetter: true, hasSetter: true, enumerable: true, configurable: true }

console.log(inspectProperty(original, '_value'));
// { property: '_value', type: 'data', value: 0, writable: true, enumerable: false, configurable: true }

// Clone preserving all attributes
const clone = cloneWithDescriptors(original);
clone.value = 42;

console.log(clone.value); // 42
console.log(Object.keys(clone)); // ['value'] (_value is still non-enumerable)

Related Methods