jsguides

Reflect.defineProperty()

Reflect.defineProperty(target, propertyKey, attributes)

Description

Reflect.defineProperty() does the same job as Object.defineProperty(): it installs or modifies a property descriptor on an object. Where the two differ is the return contract.

Object.defineProperty() returns the target object and signals failure by throwing TypeError. Reflect.defineProperty() returns true or false and only throws for type-level mistakes (the wrong target type, the wrong descriptor type). That makes it the cleaner choice when you want a success/failure branch instead of a try/catch.

Reflect.defineProperty() invokes the [[DefineOwnProperty]] internal method, the same trap a Proxy uses for its defineProperty handler. The descriptor is installed directly on target without walking the prototype chain and without invoking any setter that happens to share the property name. If you want setters to fire, use Reflect.set.

Syntax

Reflect.defineProperty(target, propertyKey, attributes)

Parameters

NameTypeDescription
targetobjectThe object on which to define or modify the property.
propertyKeystring | symbolThe name of the property. Coerced to a property key in the same way Object.defineProperty coerces it.
attributesobjectA property descriptor with zero or more of value, writable, get, set, configurable, enumerable. Must be an object.

Return value

A boolean. true if the property was successfully defined, false otherwise.

Throws

Reflect.defineProperty() throws TypeError only for type-level mistakes:

  • target is not an object (for example null or a primitive).
  • attributes is not an object.

Failure cases that Object.defineProperty would throw for (non-extensible targets, frozen targets, redefining a non-configurable property) return false instead.

Examples

Define a basic property

const obj = {};
const ok = Reflect.defineProperty(obj, 'x', { value: 7 });
console.log(ok);    // true
console.log(obj.x); // 7

The first argument is the target. The second is the key. The third is the descriptor. When the call succeeds, the property shows up on the object as if you had assigned it normally, and true comes back. A descriptor that omits writable, configurable, and enumerable produces a frozen, hidden, read-only property. These are the same defaults as Object.defineProperty uses, so be explicit when you want the looser shape.

Branch on the boolean return

Reflect.defineProperty() returns false on failure, so you can branch without a try/catch:

const target = {};

if (Reflect.defineProperty(target, 'id', { value: 42, enumerable: true })) {
  console.log('defined');
} else {
  console.log('failed');
}
// defined

The Object.defineProperty equivalent needs a try/catch because failure throws. For control-flow style code (define this property if you can, otherwise do something else) the boolean return is the cleaner fit, and it lets you keep error handling scoped to the actual type errors instead of every possible failure mode.

Define an accessor

Pass get and set instead of value to install a getter and setter. Defining the accessor does not invoke any existing setter:

const o = {};
let backing;

Reflect.defineProperty(o, 'count', {
  get() { return backing; },
  set(v) { backing = v * 2; },
  enumerable: true,
  configurable: true,
});

o.count = 5;
console.log(o.count); // 10

The descriptor is installed once, and the accessor runs whenever the property is read or written. The setter here doubles incoming values, which is why o.count = 5 reads back as 10. This is also the path that frameworks lean on to expose reactive properties, computed fields, and validation hooks on plain objects.

Use a symbol as the key

Symbol keys behave like string keys and are not coerced. That makes Reflect.defineProperty a clean way to attach hidden metadata to an object:

const id = Symbol('id');
const user = {};
Reflect.defineProperty(user, id, { value: 'usr_123' });
console.log(user[id]); // 'usr_123'

The symbol is the only handle to the property. It does not show up in Object.keys, for...in, or JSON.stringify. The same trick is what Symbol.iterator, Symbol.toPrimitive, and Symbol.asyncIterator use under the hood to wire up language-level behaviour without polluting the visible shape of an object.

Frozen and non-extensible targets return false

When the target has been sealed or frozen, Reflect.defineProperty reports the problem instead of throwing:

const frozen = Object.freeze({ a: 1 });

console.log(Reflect.defineProperty(frozen, 'b', { value: 2 }));  // false
console.log(Reflect.defineProperty(frozen, 'a', { value: 99 })); // false
console.log(frozen); // { a: 1 }

Adding a new key and modifying an existing one both return false and leave the target untouched. The same is true for objects returned by Object.preventExtensions or Object.seal, and for any attempt to redefine an existing non-configurable property, since the engine refuses the descriptor and the call reports the refusal through the boolean return instead of an exception.

Inherited setters are skipped

Reflect.defineProperty() calls [[DefineOwnProperty]], not [[Set]], so an inherited setter on the same name is bypassed:

class A { set a(v) { console.log('setter called'); } }
class B extends A {
  constructor() {
    super();
    Reflect.defineProperty(this, 'a', { value: 1, writable: true });
  }
}

new B();
// (no output, the setter is not invoked)

Class fields, super.x = y in some forms, and Object.assign go through the same internal slot. That is why defining a property this way installs a plain data property on the instance instead of triggering a setter that might be inherited from the prototype chain, which matters when you build decorators, mixins, or any code that wants to set up its own state on a subclass without surprises.

Common pitfalls

A few patterns that bite people the first time:

  • The return value is a boolean, not the object. A line like obj = Reflect.defineProperty(obj, 'x', { value: 1 }) silently replaces obj with true or false, which is almost never what you want. Use the return as a condition or discard it.
  • A false result does not mean “the property is missing on the target”. It means the descriptor was not installed: the key already exists and is non-configurable, or the target was sealed or frozen. The target is not mutated either way.
  • TypeError is still possible. If target comes from user input or could be null, keep a try/catch around the call. Reflect.defineProperty only protects you from object-shape failures, not from type-level ones.
  • Descriptor defaults are restrictive. If attributes omits configurable, enumerable, and writable, they default to false. Pass them explicitly when you want assignment-like defaults, or spread from a writable object.
  • It is not Reflect.set. If you want setters to fire and the prototype chain to be searched, use Reflect.set instead. Reflect.defineProperty skips both.

Comparison with Object.defineProperty

ScenarioReflect.defineProperty()Object.defineProperty()
Successful definetruethe target object
target is null or a primitivethrows TypeErrorthrows TypeError
attributes is null or a primitivethrows TypeErrorthrows TypeError
Target is non-extensible or frozenfalsethrows TypeError
Redefine a non-configurable propertyfalsethrows TypeError
Symbol keyworksworks
Inherited setter on the same namenot invokednot invoked

Specifications

Reflect.defineProperty() was added in ECMAScript 2015 (ES6). The behaviour is defined in the reflection chapter of ECMA-262.

Browser compatibility

Available in Chrome 49+, Edge 12+, Firefox 42+, Safari 10+, and Opera 36+. Mobile support has been in iOS Safari 10 and Chrome for Android 49 since launch. Node.js supports it from 6.0.0 onwards.

Internet Explorer does not implement Reflect. If you need IE support, polyfill Reflect.defineProperty from core-js or guard the call site.

See caniuse for the up-to-date support matrix.

See also