jsguides

Object.defineProperty()

Object.defineProperty(obj, prop, descriptor)

Object.defineProperty() lets you add or modify a property on an object with fine-grained control over its behavior. Unlike simple assignment, it lets you define whether the property is writable, enumerable, or configurable — the three attribute flags that govern property behavior.

Syntax

Object.defineProperty(obj, prop, descriptor)

Parameters

ParameterTypeDefaultDescription
objObjectThe object on which to define the property
propStringThe property name (string or symbol)
descriptorObjectThe property descriptor object

The descriptor object can include:

AttributeTypeDefaultDescription
valueanyundefinedThe property’s value
writableBooleanfalseIf true, the value can be changed
enumerableBooleanfalseIf true, the property appears in for...in loops
configurableBooleanfalseIf true, the property can be deleted or modified

Examples

Adding a read-only property

const user = {};

Object.defineProperty(user, 'name', {
  value: 'Alice',
  writable: false,
  enumerable: true,
  configurable: false
});

console.log(user.name);
// 'Alice'

user.name = 'Bob';
console.log(user.name);
// 'Alice' — unchanged because writable is false

Read-only properties block direct assignment, but you may want a property whose value is computed on access rather than stored. Getters give you exactly that — a function that runs every time the property is read, without needing a setter. This is useful for derived values that depend on other internal state.

Creating a getter-only property

const product = {
  _price: 99
};

Object.defineProperty(product, 'price', {
  get() {
    return this._price;
  },
  enumerable: true,
  configurable: true
});

console.log(product.price);
// 99

product.price = 50;
console.log(product.price);
// 99 — setter not defined, assignment ignored

Getters and setters control how a value is produced and updated. A different concern is visibility — whether a property shows up in Object.keys() or for...in loops. The enumerable flag handles that. Hiding internal properties like API keys from enumeration keeps object output clean and prevents accidental exposure in serialization.

Making a property invisible to loops

const config = {
  apiKey: 'secret123'
};

Object.defineProperty(config, 'apiKey', {
  enumerable: false
});

console.log(Object.keys(config));
// [] — apiKey is hidden

for (const key in config) {
  console.log(key);
}
// (nothing) — property is not enumerable

Common Patterns

Factory pattern for private data: Use closures with Object.defineProperty() to create true private fields before ES6 classes existed.

Immutability: Combine Object.defineProperty() with Object.freeze() to create fully immutable objects.

Reactive data: Define getters and setters to trigger updates when a property changes — the basis for many reactivity systems.

Data descriptors vs accessor descriptors

A property descriptor is either a data descriptor (has value and/or writable) or an accessor descriptor (has get and/or set). You cannot mix the two — providing both value and get in the same descriptor throws a TypeError. When you define a property with a plain assignment (like obj.x = 1), JavaScript internally creates a data descriptor with writable: true, enumerable: true, and configurable: true. Object.defineProperty() defaults all boolean flags to false when they are omitted, so a property defined without specifying writable: true will silently ignore assignment attempts in non-strict mode and throw in strict mode.

Non-configurable properties

Setting configurable: false prevents the property from being deleted with delete and from being redefined via Object.defineProperty(). The one exception: a non-configurable, writable data property can still have writable changed from true to false — but not back again. Once writable: false is set on a non-configurable property, it is truly locked. Attempting to change any other attribute of a non-configurable property throws a TypeError.

Checking property attributes

Use Object.getOwnPropertyDescriptor() to inspect the current attributes of a property:

const obj = { name: "Alice" };
Object.getOwnPropertyDescriptor(obj, "name");
// { value: 'Alice', writable: true, enumerable: true, configurable: true }

Object.defineProperty(obj, "id", { value: 42, writable: false });
Object.getOwnPropertyDescriptor(obj, "id");
// { value: 42, writable: false, enumerable: false, configurable: false }

When defineProperty() is worth the extra detail

Object.defineProperty() is the right tool when a property needs behavior that plain assignment cannot express. That includes read-only values, hidden fields, getters, and properties that should resist deletion or redefinition.

It is also useful when you are building APIs that should expose a stable surface. By setting enumerable, writable, and configurable deliberately, you can make an object easier to inspect and harder to misuse.

See Also