Object.defineProperty()

The low-level way to add a property, and the one place in the language where omitting an option means "off". A property defined this way is invisible to Object.keys unless you say otherwise.

Object static methodES5 (2009)Live demo
Common call
Object.defineProperty(o, "a", {value: 1, enumerable: true})
Returns
the same object, mutated
Replaces
plain assignment, when you need control over the flags
Watch out
writable, enumerable and configurable all default to FALSE
Object.defineProperty(objectobject — The object to modify. It is mutated and returned.type: object · required, keykey — The property key.type: string | symbol · required, descriptordescriptor — Either a DATA descriptor with value and writable, or an ACCESSOR descriptor with get and set. Never both. enumerable and configurable apply to either, and every omitted flag defaults to false.type: object · required)
→ object

Demo

Live evaluation
Try:
Inputs
vnumberthe property value
enumberenumerable: 1 for yes, 0 for no
Output
(o => [Object.keys(o), o.a])(Object.defineProperty({}, 'a', { value: 1, enumerable: Boolean(0) }))
[[], 1]

The pair is the object own keys and the value read back. With enumerable false — the DEFAULT if you leave it out — the keys array is empty while o.a still returns the value. The property genuinely exists and is readable; it is simply invisible to Object.keys, Object.entries, JSON.stringify, the spread operator and for...in. That asymmetry is the single most surprising thing about this method, and it is why a property added here can appear to vanish from serialised output while working perfectly in code.

Parameters

NameTypeRequiredDescription
objectobjectyesThe object to modify. It is mutated and returned.
keystring | symbolyesThe property key.
descriptorobjectyesEither a DATA descriptor with value and writable, or an ACCESSOR descriptor with get and set. Never both. enumerable and configurable apply to either, and every omitted flag defaults to false.

Return value

object — The same object, modified in place. The property is created or redefined according to the descriptor.

Common patterns

Add a hidden internal property
Present in code, absent from JSON.
Object.defineProperty(o, "_cache", {value: new Map(), writable: true});
Define a computed property
An accessor descriptor instead of a data one.
Object.defineProperty(o, "full", {
  get() { return `${this.first} ${this.last}`; },
  enumerable: true,
});
Copy properties with their flags intact
What Object.assign cannot do.
Object.defineProperties(target, Object.getOwnPropertyDescriptors(source));

Examples

1. Hidden by default
const o = {}; Object.defineProperty(o, "a", {value: 1}); Object.keys(o)
Returns
[]
2. But readable
o.a
Returns
1
3. A literal is visible
Object.keys({a: 1})
Returns
['a']
4. Read-only by default
"use strict"; const o = {}; Object.defineProperty(o, "a", {value: 1}); o.a = 2
Returns
TypeError: Cannot assign to read only property 'a'
5. And unredefinable
Object.defineProperty(o, "a", {value: 2})
Returns
TypeError: Cannot redefine property: a
6. A getter
const o = {}; Object.defineProperty(o, "a", {get: () => 7}); o.a
Returns
7

Pitfalls

1. Every flag defaults to false
Unlike a property created by assignment, which is writable, enumerable and configurable, a defined property is none of those unless stated. The property then quietly disappears from JSON.stringify and from spread copies, which is usually discovered much later.
Vanishes from JSON
const o = {};
Object.defineProperty(o, "a", {value: 1});
JSON.stringify(o)
'{}'
Opt in explicitly
Object.defineProperty(o, "a", {value: 1, enumerable: true, writable: true, configurable: true})
'{"a":1}'
2. configurable: false is permanent
A non-configurable property cannot be redefined or deleted, ever, for the life of that object. Getting the descriptor wrong the first time means rebuilding the object — there is no way to undo it.
Locked in
Object.defineProperty(o, "a", {value: 1});
Object.defineProperty(o, "a", {value: 2})
TypeError: Cannot redefine property: a
Allow changes
Object.defineProperty(o, "a", {value: 1, configurable: true})
redefinable
3. You cannot mix value and get
A descriptor is either a data descriptor or an accessor descriptor. Supplying value alongside get — or writable alongside set — is a TypeError, which is easy to hit when building a descriptor programmatically.
Both kinds
Object.defineProperty({}, "a", {value: 1, get: () => 2})
TypeError: Invalid property descriptor. Cannot both specify accessors and a value or writable attribute
Pick one
Object.defineProperty({}, "a", {get: () => 2})
an accessor
4. It defines rather than assigns, which is sometimes the point
Assignment runs an inherited setter; defineProperty does not. That makes it the correct tool for overriding a property whose prototype defines a setter — and a surprise if you expected the setter to run.
Setter skipped
const p = {set a(v) { console.log("set"); }};
const o = Object.create(p);
Object.defineProperty(o, "a", {value: 1})
nothing logged
Assign to trigger it
o.a = 1
"set" logged

When to use

Use it
  • Properties that should not appear in JSON or in Object.keys
  • Read-only or non-deletable properties
  • Getters and setters added after the object exists
  • Copying properties with their descriptors intact
Reach for something else
  • An ordinary property → plain assignment or an object literal
  • Several properties at once → Object.defineProperties
  • You want everything locked → Object.freeze is simpler
  • Genuinely private state → a # private field in a class

Notes

Complexity
O(1) per property
Return
The same object, mutated
CPython impl
V8: Builtins-object-defineproperty
Memory
No allocation beyond the property slot
Thread-safe
Single-threaded; a non-configurable definition is irreversible

FAQ

Because enumerable defaults to false. JSON.stringify, Object.keys, Object.entries, spread and for...in all skip non-enumerable properties. Add enumerable: true if the property is part of the object visible data.

Object.defineProperty(o, "a", {value: 1, enumerable: true});

History

ES5
defineProperty, defineProperties and the descriptor model added, formalising property attributes.
ES2015
Symbols became valid keys, and get/set shorthand in class bodies covered most accessor uses.