completefrontend
Open lab My progress
THE JAVASCRIPT FIELD GUIDE

Property flags & descriptors

Learn how JavaScript property descriptors control writable, enumerable, and configurable behavior, and how definition differs from assignment.

By the end, you can
  • 01
    Read a property's flagsUse getOwnPropertyDescriptor and getOwnPropertyDescriptors to see the value and the three flags of a property.
  • 02
    Set the flags yourselfUse Object.defineProperty for one property, and Object.defineProperties or Object.create for many.
  • 03
    Predict what the flags allowTell whether an assignment, a delete, a list like Object.keys, or a new definition works or fails.

Every property has three flags

You already use object properties all the time: user.name, settings.theme, cart.total. So far, a property has looked like a name and a value. JavaScript keeps a little more for each property. It also keeps three yes-or-no settings, called flags.

A property descriptor is a plain object that lists the value and the flags of one property. This lesson is about data descriptors. A data descriptor has four parts:

  • value: the value the property holds.
  • writable: can assignment, like obj.x = 2, change the value?
  • enumerable: does the property show up when JavaScript lists properties?
  • configurable: can you delete the property or change its flags?

There is a second kind, the accessor descriptor. It has no value. Instead it has a get function, which runs when you read the property, and a set function, which runs when you assign to it. The next lesson, Getters & setters, is about this kind. Here is a quick look.

A property that runs functions instead of holding a valuePop out in the code editor (opens in a new tab)JavaScript
let displayName = "Asha";
const user = {};
Object.defineProperty(user, "name", {
  get() { return displayName; },
  set(value) { displayName = value; },
  enumerable: true,
});
console.log(user.name);
user.name = "Mina";
console.log(user.name);

The real name lives in the separate displayName variable. Line 7 reads user.name, so get runs and prints Asha. Line 8 assigns, so set runs and stores Mina. Line 9 prints Mina. One descriptor uses either value and writable, or get and set. It can't mix the two.

Real-life analogyA hidden settings panel

Picture a small panel of switches behind each property. Turn off writable, and the value is read-only. Turn off enumerable, and the property is left out of lists. Turn off configurable, and the panel itself is locked.

In real life: A switch marked writable
In JavaScript: Can assignment change the value?
In real life: A switch marked enumerable
In JavaScript: Does the property show up in lists?
In real life: A switch marked configurable
In JavaScript: Can you delete the property or flip the other switches?

The table shows each flag. Read across a row to see what happens when that flag is true and when it is false.

What each flag does
FlagWhat happens when it is trueWhat happens when it is false
writableAssignment can change the value.Assignment can't change the value. In strict mode it throws TypeError. In sloppy mode nothing happens.
enumerableThe property shows up in Object.keys, for...in loops, spread copies, and JSON.stringify.You can still read the property by name, but those lists skip it.
configurableYou can delete the property and change its flags.You can't delete it or change its flags. The one change allowed is writable: true to false.

These flags explain things you may have seen. You can't overwrite Math.PI. Some properties never show up in Object.keys. Libraries can keep their own notes on an object without those notes appearing in loops or in JSON text.

Object.defineProperty sets the flags

INTERACTIVE

Plain assignment, like obj.x = 1, sets a value. You don't choose any flags. Object.defineProperty lets you set the value and the flags yourself. We call this defining a property. It takes three things:

  • the object, like account
  • the property name, like "id"
  • a descriptor object with the value and flags
Define one property with its flagsPop out in the code editor (opens in a new tab)JavaScript
"use strict";
const account = {};

Object.defineProperty(account, "id", {
  value: 42,
  writable: false,
  enumerable: true,
  configurable: false,
});

console.log(account.id); // 42

Lines 4 to 9 give account an id property:

  • value: 42: id holds 42.
  • writable: false: assignment can't change it.
  • enumerable: true: it shows up in lists like Object.keys.
  • configurable: false: you can't delete it or change its flags later.
The account cart holds id: 42. An arrow points to the three flags for id: writable false, so it can't change; enumerable true, so it gets listed; configurable false, so it can't be deleted.id: 42accountthe flags for idwritable: falsecan't changeenumerable: truegets listedconfigurable: falsecan't delete
The id property holds 42 and three flags. Each flag answers one yes-or-no question about id.

Line 1 turns on strict mode, the stricter set of JavaScript rules. This site's code runs in strict mode. In strict mode, a write that isn't allowed throws a TypeError, an error you can catch. Without strict mode (people call it sloppy mode), the same write does nothing and gives no error. The old value stays.

Flag tester
The strict code being testedPop out in the code editor (opens in a new tab)JavaScript
"use strict";const panel = {};Object.defineProperty(panel, "choice", {  value: "open",  writable: true,  enumerable: true,  configurable: true,}); panel.choice = "changed";delete panel.choice;Object.keys(panel);JSON.stringify(panel);
What really happensdescriptor
  • Before{"value":"open","writable":true,"enumerable":true,"configurable":true}
    assignchanged
    deletetrue
    keys(none)
    json{}
    redefineok
    After{"writable":false,"enumerable":false,"configurable":false}
Try it yourself
Flags:Try:

A new object each time. With writable=true, enumerable=true, configurable=true: assign: changed

Turn each flag on or off, then pick an action. Each time, the tester makes a new object and shows what really happens in strict mode.

Try all three flags. Notice one surprise. With enumerable off, you can still read panel.choice. The flag only decides whether lists like Object.keys include it. And configurable is the flag that decides whether delete and redefining work.

Read a property's flags

STEP THROUGH

JavaScript gives you two ways to read descriptors:

  • Object.getOwnPropertyDescriptor(obj, name) returns the descriptor of one property.
  • Object.getOwnPropertyDescriptors(obj) returns the descriptors of all the object's properties at once.

Both look only at the object's own properties: the ones stored on the object itself. They don't look at its prototype, the object it borrows properties from.

Flags from a literal vs from defineProperty
Step 0 of 4Ready
Your turn: follow the blue line

Make one property with a literal and one with defineProperty. Then compare their flags.

Running in
  1. script
Next: line 2
"use strict";Object.defineProperty(normal, "quiet", { value: 2 }); console.log(Object.getOwnPropertyDescriptor(normal, "visible"));console.log(Object.getOwnPropertyDescriptor(normal, "quiet"));
CallStoreChangeResultBlue line: runs next. Ran: already executed.
Recent returnsNothing yet. Start with the blue line.
A guided replay recorded from real JavaScript calls, not an engine debugger. Step follows executed statements; Back reviews a snapshot. Reset starts a fresh run.

You can read the flags of built-in properties too. Math.PI has writable: false, enumerable: false, and configurable: false. So:

  • strict code can't change it,
  • Object.keys(Math) doesn't list it,
  • and you can't delete it.
Read the flags of Math.PIPop out in the code editor (opens in a new tab)JavaScript
const pi = Object.getOwnPropertyDescriptor(Math, "PI");
console.log(pi.value);
console.log(pi.writable);
console.log(pi.configurable);

Line 1 saves the descriptor of Math.PI in pi. Line 2 prints the value, 3.141592653589793. Lines 3 and 4 print false and false.

Own means stored on the object itself

getOwnPropertyDescriptor only finds properties stored right on the object you pass. If an object gets a property from its prototype, you get undefined. To read that property's flags, pass the prototype object instead.

Default flags: assignment turns them on, defineProperty turns them off

This is the rule that surprises people most. How you make a property decides its starting flags:

  • An object literal, like { x: 1 }, or an assignment, like obj.x = 1, makes all three flags true.
  • Object.defineProperty makes every flag you leave out false.

The table lists each way. Read a row to see the flags you get.

The flags you get from each way of making a property
When you writeThe flags you get
Object literal: { x: 1 }writable: true, enumerable: true, configurable: true
Assignment: obj.x = 1If the assignment works, all three flags are true.
Object.defineProperty(obj, "x", { value: 1 })Every flag you leave out is false. The property is read-only, left out of lists, and locked.
Same value, different flagsPop out in the code editor (opens in a new tab)JavaScript
const assigned = {};
assigned.price = 3;
const defined = {};
Object.defineProperty(defined, "price", { value: 3 });
console.log(Object.getOwnPropertyDescriptor(assigned, "price").writable);
console.log(Object.getOwnPropertyDescriptor(defined, "price").writable);

Line 2 uses assignment, so line 5 prints true for writable. Line 4 gives only a value, so line 6 prints false. The other two flags are false too, unless you write them in the descriptor.

Left: assigned.price = 3 makes price: 3 with writable true and all three flags true. Right: Object.defineProperty with only value: 3 makes price: 3 with writable false and all three flags false.assignmentassigned.price = 3price: 3assignedwritable: trueall 3 flags trueObject.defineProperty{ value: 3 }price: 3definedwritable: falseall 3 flags false
Both carts hold price: 3, but the flags differ. Assignment turns all three flags on. defineProperty turns off every flag you leave out.

This is on purpose. defineProperty is for when you want exact control. If it turned flags on by itself, you could open up a property by mistake.

A failed write, in sloppy and strict modePop out in the code editor (opens in a new tab)JavaScript
const sloppy = {};
Object.defineProperty(sloppy, "x", { value: 1, writable: false });
sloppy.x = 2;
console.log(sloppy.x);

(function strictMode() {
  "use strict";
  const strict = {};
  Object.defineProperty(strict, "x", { value: 1, writable: false });
  try { strict.x = 2; } catch (error) { console.log(error.name); }
})();

Lines 1 to 4 run in sloppy mode. Line 3 tries to change a read-only property. Nothing happens and there's no error, so line 4 prints 1. Lines 6 to 11 run in strict mode. The same write on line 10 throws, and the catch prints TypeError. This course's code is strict, so expect the error.

Define many properties at once

STEP THROUGH

A descriptor map is an object where each key is a property name and each value is that property's descriptor. Two tools take a descriptor map:

  • Object.defineProperties(obj, map) adds the properties to an object you already have.
  • Object.create(proto, map) makes a new object with those properties.
Define several properties with descriptor maps
Step 0 of 5Ready
Your turn: follow the blue line

Make an object with Object.create, add more with Object.defineProperties, then compare Object.keys with every descriptor.

Running in
  1. script
Next: line 2
"use strict";  id: { value: 42, enumerable: true },  role: { value: "admin", enumerable: true },}); Object.defineProperties(user, {  token: { value: "abc", writable: false },  label: { value: "Ada", writable: true, enumerable: true },}); console.log(Object.keys(user));console.log(Object.getOwnPropertyDescriptors(user));
CallStoreChangeResultBlue line: runs next. Ran: already executed.
Recent returnsNothing yet. Start with the blue line.
A guided replay recorded from real JavaScript calls, not an engine debugger. Step follows executed statements; Back reviews a snapshot. Reset starts a fresh run.

This is handy when an object needs some properties that everyone sees and some hidden ones. In the walkthrough, the descriptor for token leaves out enumerable, so it is false. You can still read user.token, but Object.keys and JSON.stringify skip it.

The user cart holds token: "abc", plus id, role, and label. Object.keys(user) gives ["id","role","label"], with no token. user.token still gives "abc".token: "abc"useralso id, role, labelObject.keys(user)["id","role","label"]no tokenuser.token"abc"still readable
token has enumerable: false, so Object.keys skips it. Reading user.token by name still works.
Copy an object and keep its flagsPop out in the code editor (opens in a new tab)JavaScript
const original = {};
Object.defineProperties(original, {
  id: { value: 42, writable: false, enumerable: true },
  token: { value: "abc", enumerable: false },
});
const clone = Object.defineProperties({}, Object.getOwnPropertyDescriptors(original));
const spread = { ...original };
console.log(Object.keys(clone));
console.log(Object.getOwnPropertyDescriptor(clone, "token").enumerable);
console.log(Object.getOwnPropertyDescriptor(spread, "id").writable);

Line 6 makes clone. getOwnPropertyDescriptors gives a descriptor map, and defineProperties uses it. So clone keeps token hidden and id read-only. Line 7 makes spread with the spread syntax ..., which copies only listed values. Its id is a normal property, so line 10 prints true for writable. Use the descriptor way when the copy must follow the same rules as the original.

The full walkthrough codePop out in the code editor (opens in a new tab)JavaScript
"use strict";
const user = Object.create(null, {
  id: { value: 42, enumerable: true },
  role: { value: "admin", enumerable: true },
});

Object.defineProperties(user, {
  token: { value: "abc", writable: false },
  label: { value: "Ada", writable: true, enumerable: true },
});

console.log(Object.keys(user));
console.log(Object.getOwnPropertyDescriptors(user));

Assignment vs definition

STEP THROUGH

Assignment and definition both put a property on an object. But they follow different rules:

  • Assignment, like child.name = "Ada", first looks for name on the object and its prototype. If the prototype has a setter (a set function for that name), the setter runs. If the prototype has a read-only property with that name, the assignment fails.
  • Definition, with Object.defineProperty, puts the property straight onto the object. It doesn't check the prototype.
Real-life analogyWriting on a notebook page

You go to write a name in a notebook. On an earlier page, a note says "Write all names in the guest list." So you follow the note instead. Gluing in a new labeled page is different. You skip the notes and add the page right where you want it.

In real life: Writing on the page in the usual way
In JavaScript: Assignment: child.name = "Ada"
In real life: Following a note on an earlier page
In JavaScript: An inherited setter that runs during assignment
In real life: Gluing in a new labeled page
In JavaScript: Definition: Object.defineProperty(child, "name", ...)
Assignment runs the setter; definition doesn't
Step 0 of 9Ready
Your turn: follow the blue line

Compare assignment, which checks the prototype, with definition, which doesn't.

Running in
  1. script
Next: line 3
"use strict";const log = [];  set name(value) { log.push("setter:" + value); }};Object.defineProperty(proto, "locked", { value: 1, writable: false }); const child = Object.create(proto);child.name = "Ada";Object.defineProperty(child, "name", { value: "Ada", enumerable: true }); try { child.locked = 2; } catch (error) { log.push(error.name); }Object.defineProperty(child, "locked", { value: 2, enumerable: true }); console.log(log);console.log(child.name, child.locked);
CallStoreChangeResultBlue line: runs next. Ran: already executed.
Recent returnsNothing yet. Start with the blue line.
A guided replay recorded from real JavaScript calls, not an engine debugger. Step follows executed statements; Back reviews a snapshot. Reset starts a fresh run.
Assignment and definition side by sidePop out in the code editor (opens in a new tab)JavaScript
"use strict";
const log = [];
const proto = {
  set name(value) { log.push("setter:" + value); }
};
Object.defineProperty(proto, "locked", { value: 1, writable: false });

const child = Object.create(proto);
child.name = "Ada";
Object.defineProperty(child, "name", { value: "Ada", enumerable: true });

try { child.locked = 2; } catch (error) { log.push(error.name); }
Object.defineProperty(child, "locked", { value: 2, enumerable: true });

console.log(log);
console.log(child.name, child.locked);

Line 9 assigns, so the setter on proto runs and adds "setter:Ada" to log. Line 10 defines, so child gets its own name, and the setter doesn't run.

Left: child.name = "Ada" goes to the set name() setter in the proto cart. The setter runs and child gets no own name. Right: defineProperty(child, …) puts name: "Ada" straight into the child cart. The setter is skipped.assignmentchild.name = "Ada"set name()protosetter runschild has no own namedefinitiondefineProperty(child, …)name: "Ada"childown name addedsetter skipped
Assignment finds the setter on proto and runs it. Definition skips the setter and puts name right on child.

Line 12 assigns to locked. The prototype's locked is read-only, so the assignment throws TypeError. Line 13 defines locked on child and works.

This matters for objects made from prototypes, like class instances and objects from libraries. Assign when you want the object's setters to run. Define when you need exact flags on the object itself.

Where you'll use this

Most properties don't need special flags. Descriptors help in a few places where a normal property allows too much:

  • Read-only constants. A library can share a version number that other code can't overwrite by mistake.
  • Hidden notes. Frameworks often store their own notes on your objects. With enumerable: false, those notes stay out of Object.keys, spread copies, and JSON text.
  • A property that can change kind later. With configurable: true, you can later replace a plain value with get and set functions, as in the next lesson.
  • Finding bugs. Reading a property's flags tells you why a write, a loop, or a delete acts differently than you expected.
Allowed or blocked?
  • Assign to a property with writable: true
  • In strict mode, assign to an own property with writable: false
  • Read obj.secret when secret has enumerable: false
  • See a non-enumerable property in Object.keys(obj)
  • Delete a property with configurable: true
  • Change configurable: false back to true
  • Change writable: true to false on a property with configurable: false
  • Use defineProperty to add an own property when the prototype has a setter for that name
Try it yourself
0 of 8 correct

Sort each action by whether the flags allow it. Assume strict mode, so a failed write throws.

Choose a category for every card. You can change an answer at any time; Reset clears them all.

Common mistakes and the one-way lock

"Non-enumerable means private."

No. Lists like Object.keys skip it. But anyone who knows the name can read it, and getOwnPropertyDescriptors shows it.

"writable: false freezes the whole object."

No. It makes one property's value read-only. You can still add or change other properties. Freezing a whole object comes later in this module.

"configurable: false means the value can never change."

No. If writable is still true, assignment can still change the value. configurable decides only whether you can delete the property or change its flags.

"You can unlock a property later."

No. configurable: false works one way only. You may still change writable from true to false, but you can't change it back.

"Assignment and defineProperty do the same thing."

No. Assignment can run a setter from the prototype, and a read-only property on the prototype can block it. Definition puts the property straight onto the object.

The one-way rule

Once a property has configurable: false, other code can trust that it stays that way. You can make it stricter by changing writable: true to false. You can never make it looser again.

Lock once, never unlockPop out in the code editor (opens in a new tab)JavaScript
"use strict";
const account = {};
Object.defineProperty(account, "id", {
  value: 1,
  writable: true,
  configurable: false,
});
Object.defineProperty(account, "id", { writable: false });
console.log(Object.getOwnPropertyDescriptor(account, "id").writable);
try { Object.defineProperty(account, "id", { writable: true }); } catch (error) {
  console.log(error.name);
}

Lines 3 to 7 define id with configurable: false. Line 8 changes writable from true to false. That is allowed, because it only makes the property stricter. Line 9 prints false. Line 10 tries to turn writable back on, so it throws, and line 11 prints TypeError. You can still read the value.

Practice: property descriptors

5 EXERCISES
Exercise 1 · Warm-upMake a read-only property

Read the code and predict the two lines it prints. This is the smallest useful descriptor: a value plus flags.

Starter codePop out in the code editor (opens in a new tab)JavaScript
"use strict";
const profile = {};
Object.defineProperty(profile, "id", {
  value: 101,
  writable: false,
  enumerable: true,
  configurable: true,
});
try { profile.id = 202; } catch (error) { console.log(error.name); }
console.log(profile.id);

Answer, then press Check. Spacing and letter case don’t matter.

    Exercise 2 · PracticePredict what happens with left-out flags

    Work out the three lines it prints without running the code.

    Starter codePop out in the code editor (opens in a new tab)JavaScript
    "use strict";
    const user = {};
    Object.defineProperty(user, "token", { value: "abc" });
    console.log(Object.keys(user).length);
    console.log(user.token);
    try { delete user.token; } catch (error) { console.log(error.name); }

    Answer, then press Check. Spacing and letter case don’t matter.

      Exercise 3 · PracticeRead a built-in property's flags

      The code uses Object.getOwnPropertyDescriptor on a built-in property. Which two values does it print?

      Starter codePop out in the code editor (opens in a new tab)JavaScript
      const descriptor = Object.getOwnPropertyDescriptor(Math, "PI");
      console.log(descriptor.writable);
      console.log(descriptor.configurable);

      Answer, then press Check. Spacing and letter case don’t matter.

        Exercise 4 · PracticeHide a property from Object.keys

        Which property name does Object.keys(settings) show?

        Starter codePop out in the code editor (opens in a new tab)JavaScript
        const settings = { theme: "dark" };
        Object.defineProperty(settings, "token", {
          value: "abc",
          enumerable: false,
        });
        console.log(Object.keys(settings));
        console.log(settings.token);

        Answer, then press Check. Spacing and letter case don’t matter.

          Exercise 5 · ChallengeAssignment vs definition with a setter on the prototype

          What does the setter add to log? Why doesn't the definition line add anything?

          Starter codePop out in the code editor (opens in a new tab)JavaScript
          "use strict";
          const log = [];
          const proto = { set name(value) { log.push(value); } };
          const child = Object.create(proto);
          child.name = "Ada";
          Object.defineProperty(child, "name", { value: "Own Ada", enumerable: true });
          console.log(log.join(","));
          console.log(child.name);

          Answer, then press Check. Spacing and letter case don’t matter.

            Quiz: property flags

            7 QUESTIONS
            Lesson quiz · 7 questionsScore: first tries count
            1. Question 1 of 7Which call returns the descriptor of one own property?

              Choose an answer to see the explanation.

            2. Question 2 of 7What does this print?

              Read the code, then predictPop out in the code editor (opens in a new tab)JavaScript
              const box = { x: 1 };
              console.log(Object.getOwnPropertyDescriptor(box, "x").writable);

              Choose an answer to see the explanation.

            3. Question 3 of 7What does Object.keys give for a property made with only a value?

              Read the code, then predictPop out in the code editor (opens in a new tab)JavaScript
              const box = {};
              Object.defineProperty(box, "x", { value: 1 });
              console.log(Object.keys(box).length);

              Choose an answer to see the explanation.

            4. Question 4 of 7Which flag decides whether delete obj.x can remove a property?

              Choose an answer to see the explanation.

            5. Question 5 of 7What prints when strict code assigns to a read-only property on the prototype?

              Read the code, then predictPop out in the code editor (opens in a new tab)JavaScript
              "use strict";
              const parent = {};
              Object.defineProperty(parent, "x", { value: 1, writable: false });
              const child = Object.create(parent);
              try { child.x = 2; } catch (error) { console.log(error.name); }

              Choose an answer to see the explanation.

            6. Question 6 of 7Which sentence about configurable: false is true?

              Choose an answer to see the explanation.

            7. Question 7 of 7What prints when definition is used and the prototype has a setter?

              Read the code, then predictPop out in the code editor (opens in a new tab)JavaScript
              const log = [];
              const proto = { set name(value) { log.push(value); } };
              const child = Object.create(proto);
              Object.defineProperty(child, "name", { value: "Ada" });
              console.log(log.length);

              Choose an answer to see the explanation.

            Key takeaways

            • A property descriptor lists a property's value and its three flags.
            • writable decides whether assignment can change the value.
            • enumerable decides whether lists like Object.keys include the property.
            • configurable decides whether you can delete the property or change its flags.
            • Literals and assignment turn every flag on. defineProperty turns off every flag you leave out.
            • Assignment checks the prototype for setters and read-only properties. Definition puts the property straight onto the object.

            One-liner.
            A descriptor holds a property's value and the flags that say whether it can be changed, listed, deleted, or given new flags.

            Up next: Getters & setters.

            CompleteFrontend Clear concepts. Working examples.