# Proposal: Object.unseal and Object.unfreeze using a secret Symbol

**URL:** <https://es.discourse.group/t/proposal-object-unseal-and-object-unfreeze-using-a-secret-symbol/2388>\
**Category:** 💡 Ideas\
**Tags:** proposal\
**Created:** [June 18, 2025, 10:02am UTC](https://es.discourse.group/t/proposal-object-unseal-and-object-unfreeze-using-a-secret-symbol/2388 "2025-06-18T10:02:10Z")\
**Posts on this page:** 12\
**Page:** 1

<div class="post-metadata">

**Author:** ![Enzo](https://yyz2.discourse-cdn.com/free1/user_avatar/es.discourse.group/enzo/32/2484_2.png) [@Enzo](https://es.discourse.group/u/Enzo)\
**Post date:** [June 18, 2025, 10:02am UTC](https://es.discourse.group/t/proposal-object-unseal-and-object-unfreeze-using-a-secret-symbol/2388/1 "2025-06-18T10:02:10Z")

</div>

Hello everyone!

I’d like to share an early-stage idea for a reversible `Object.seal()` and `Object.freeze()` API, using a secret key mechanism (such as a `Symbol`) to restore the object’s original mutability.

## Proposal overview:

```js
const secret = Symbol();

// Seals or freezes with an optional secret
Object.seal(obj, secret); // reversible seal
Object.freeze(obj, secret) // reversible freeze

// Later, restores previous state
Object.unseal(obj, secret)
Object.unfreeze(obj, secret)

```

If the correct secret is not provided, `Object.unseal()` and `Object.unfreeze()` throw an error.

## Use cases

- Frameworks or libraries that want to protect an object during validation or construction, and then unlock it.
- Test environments or mutation guards.
- Developer tools and instrumented runtimes.

## Use case example

```javascript
// my-api.js
function MyApi() {
    const items = []
    const secret = Symbol()
    Object.freeze(items, secret)

    return {
        items,
        add: (item) => {
            Object.unfreeze(items, secret)
            items.push(item)
            Object.freeze(items, secret)
        },
    }
}

// consumer.js
const api = MyApi()
api.add('item1')
console.log(api.items) // ['item1']
api.items.push('item3') // Throws an error

```

### Why not using `Proxy`?

To replicate this behavior using a `Proxy`, you need to intercept and restrict all mutating operations manually:

```javascript
function MyApi() {
    const items = []

    const proxy = new Proxy(items, {
        get(target, prop, receiver) {
            return Reflect.get(target, prop, receiver)
        },
        set() {
            return false
        },
        deleteProperty() {
            return false
        },
        // You would also need to intercept defineProperty, setPrototypeOf, etc.
    })

    return {
        items: proxy,
        add: (item) => {
            items.push(item)
        },
    }
}

```

- Proxies require intercepting **every mutating trap** , and forgetting one introduces a hole.
- It’s more verbose and harder to reason about.
- `Object.freeze/unfreeze` clearly expresses **intentional and scoped mutability**.
- Proxy traps have runtime overhead.
- The secret-based approach uses less memory, storing only a symbol, while proxies must keep all traps and the proxy object in memory.
- Iteration methods like `for...in` and `Object.keys` may behave inconsistently depending on the proxy handler.
- Proxies are not always fully transparent, which can cause unexpected behavior.
- From a developer experience standpoint, plain objects support full property introspection, autocompletion, and static analysis.  
Proxies, on the other hand, obscure the object's shape — making tools like DevTools, autocomplete, and type systems less effective or unusable.
- Both approaches rely on keeping something private — the `target` in a Proxy, or the `secret` in `unfreeze`. But only the `secret` model enforces immutability at the object level, even if the reference leaks.

## Open questions

- Should `Object.isSealed()` / `Object.isFrozen()` report as sealed even when the object is reversible?
- How would this affect internal engine mechanics?
- Would engines have any issues storing the `Symbol` as metadata?
- Would it be better to introduce a new method, such as `Object.protect()` / `Object.unprotect()`?

## Feedback

I’d love to hear your thoughts on this direction. Especially:

- Viability concerns
- Implementation challenges (engine/runtime)
- Potential use cases I haven’t considered
- Interest from potential champions

If this idea resonates, I’d be happy to draft a full proposal repo and spec outline.

Thanks!

---

<div class="post-metadata">

**Author:** ![aclaymore](https://yyz2.discourse-cdn.com/free1/user_avatar/es.discourse.group/aclaymore/32/501_2.png) [@aclaymore](https://es.discourse.group/u/aclaymore)\
**Post date:** [June 18, 2025, 1:28pm UTC](https://es.discourse.group/t/proposal-object-unseal-and-object-unfreeze-using-a-secret-symbol/2388/2 "2025-06-18T13:28:40Z")

</div>

Worth noting that this would violate one of the core invariants of the object model:

> **[ECMAScript® 2026 Language Specification](https://tc39.es/ecma262/multipage/ecmascript-data-types-and-values.html#sec-invariants-of-the-essential-internal-methods)**
>
> Introduction
> This Ecma Standard defines the ECMAScript 2026 Language. It is the seventeenth edition of the ECMAScript Language Specification. Since publication of the first edition in 1997, ECMAScript has grown to be one of the world's most widely...

> "If [[IsExtensible]] returns false, all future calls to [[IsExtensible]] on the target must return false."

These invariants were carefully considered and many members of the committee work hard to ensure that are not broken so I think it is unlikely for a proposal to weaken them.

---

<div class="post-metadata">

**Author:** ![ljharb](https://yyz2.discourse-cdn.com/free1/user_avatar/es.discourse.group/ljharb/32/8_2.png) [@ljharb](https://es.discourse.group/u/ljharb)\
**Post date:** [June 18, 2025, 3:06pm UTC](https://es.discourse.group/t/proposal-object-unseal-and-object-unfreeze-using-a-secret-symbol/2388/3 "2025-06-18T15:06:34Z")

</div>

What would be the point of protecting something, just to then leave it completely unprotected?

---

<div class="post-metadata">

**Author:** ![Enzo](https://yyz2.discourse-cdn.com/free1/user_avatar/es.discourse.group/enzo/32/2484_2.png) [@Enzo](https://es.discourse.group/u/Enzo)\
**Post date:** [June 18, 2025, 3:26pm UTC](https://es.discourse.group/t/proposal-object-unseal-and-object-unfreeze-using-a-secret-symbol/2388/4 "2025-06-18T15:26:04Z")

</div>

If by "something" you mean anything in life, you might want to protect yourself with a bulletproof vest—and then take it off when you're safe at home.

If you're referring to the context of an object in JavaScript, you might want to prevent the consumer API from mutating the object.

---

<div class="post-metadata">

**Author:** ![aclaymore](https://yyz2.discourse-cdn.com/free1/user_avatar/es.discourse.group/aclaymore/32/501_2.png) [@aclaymore](https://es.discourse.group/u/aclaymore)\
**Post date:** [June 18, 2025, 5:45pm UTC](https://es.discourse.group/t/proposal-object-unseal-and-object-unfreeze-using-a-secret-symbol/2388/5 "2025-06-18T17:45:52Z")

</div>

If the goal is simpler code then I would probably create a concrete type to represent this, something like:

```javascript
class LockableData {
  #data = new Map();
  #locked = false;
  #secret;

  constructor(secret) { this.#secret = secret }

  get(key) { return this.#data.get(key) }

  set(key, val) {
    if (this.#locked) throw new Error("locked");
    this.#data.set(key, val);
  }

  lock(secret) {
    if (this.#locked) throw new Error("locked");
    if (secret !== this.#secret) throw new Error("wrong secret");
    this.#locked = true;
    return () => { this.#locked = false; };
  }  
}

```

Instead of using an exotic object.

---

<div class="post-metadata">

**Author:** ![bakkot](https://yyz2.discourse-cdn.com/free1/user_avatar/es.discourse.group/bakkot/32/22_2.png) [@bakkot](https://es.discourse.group/u/bakkot)\
**Post date:** [June 18, 2025, 6:56pm UTC](https://es.discourse.group/t/proposal-object-unseal-and-object-unfreeze-using-a-secret-symbol/2388/6 "2025-06-18T18:56:15Z")

</div>

I think this would add a lot of complexity to the object model in engines for relatively little benefit, with that cost potentially affecting even code which doesn't use this new kind of object, so it is unlikely to go anywhere. Performance of the object implementation in engines is crucially important which means there should be an extremely high bar for making it more complicated.

---

<div class="post-metadata">

**Author:** ![theScottyJam](https://yyz2.discourse-cdn.com/free1/user_avatar/es.discourse.group/thescottyjam/32/616_2.png) [@theScottyJam](https://es.discourse.group/u/theScottyJam)\
**Post date:** [June 18, 2025, 11:03pm UTC](https://es.discourse.group/t/proposal-object-unseal-and-object-unfreeze-using-a-secret-symbol/2388/7 "2025-06-18T23:03:55Z")

</div>

Perhaps the specific solution doesn't work, but I do find the problem to be solved intriguing - the idea of having a piece of data that, from outside the class you only want to provide read access to it, but from inside the class you want to be able to modify the data.

There could be other ways to solve a problem like this. We could, for example, provide a "read-only view" into an object - basically a proxy, but we provide a high-level API to make these views so developers don't have to manually configure the proxy.

Or something. Not really convinced on that option either, but it does seem like it would be nice to have some way of exposing data without also granting the ability to edit it.

---

<div class="post-metadata">

**Author:** ![ljharb](https://yyz2.discourse-cdn.com/free1/user_avatar/es.discourse.group/ljharb/32/8_2.png) [@ljharb](https://es.discourse.group/u/ljharb)\
**Post date:** [June 19, 2025, 12:44am UTC](https://es.discourse.group/t/proposal-object-unseal-and-object-unfreeze-using-a-secret-symbol/2388/8 "2025-06-19T00:44:57Z")

</div>

You provide a frozen object, and then copy it to a mutable one later?

---

<div class="post-metadata">

**Author:** ![Enzo](https://yyz2.discourse-cdn.com/free1/user_avatar/es.discourse.group/enzo/32/2484_2.png) [@Enzo](https://es.discourse.group/u/Enzo)\
**Post date:** [June 19, 2025, 6:57am UTC](https://es.discourse.group/t/proposal-object-unseal-and-object-unfreeze-using-a-secret-symbol/2388/9 "2025-06-19T06:57:53Z")

</div>

> [@aclaymore](#):
>
> If the goal is simpler code then I would probably create a concrete type to represent this, something like:

The goal is to have the ability to create read-only plain objects outside the class or function that creates them, without relying on complex syntax, proxies, or duplicating data.

I thought that `Object.unfreeze` would be the easiest way to achieve this without introducing extra syntax or more complex changes. But as you pointed out, it violates the specification, and engines are optimized to work with non-extensible objects.

> [@bakkot](#):
>
> I think this would add a lot of complexity to the object model in engines for relatively little benefit, with that cost potentially affecting even code which doesn't use this new kind of object, so it is unlikely to go anywhere. Performance of the object implementation in engines is crucially important which means there should be an extremely high bar for making it more complicated.

I understand, that my solution is not the best aproach. But I still think that having read-only data/objects outside the constructor is a powerful idea.

> [@ljharb](#):
>
> You provide a frozen object, and then copy it to a mutable one later?

Then you end up with duplicate data, which is difficult to maintain at the API level, doubles the memory usage, and can introduce runtime overhead when copying large objects.

---

<div class="post-metadata">

**Author:** ![mhofman](https://avatars.discourse-cdn.com/v4/letter/m/f14d63/32.png) [@mhofman](https://es.discourse.group/u/mhofman)\
**Post date:** [August 2, 2025, 6:08am UTC](https://es.discourse.group/t/proposal-object-unseal-and-object-unfreeze-using-a-secret-symbol/2388/10 "2025-08-02T06:08:11Z")

</div>

Could you clarify the motivation to directly expose a private state object if you want to restrict public access to that state?

Why not expose a public facet that only contains the API (methods / accessors) meant to be used publicly. This is the basic of encapsulation. No need for proxies.

The example you give is exposing an array. Does you use cases require the whole array API to be the public facing API?

---

<div class="post-metadata">

**Author:** ![natewatson999](https://yyz2.discourse-cdn.com/free1/user_avatar/es.discourse.group/natewatson999/32/2514_2.png) [@natewatson999](https://es.discourse.group/u/natewatson999)\
**Post date:** [August 14, 2025, 11:42pm UTC](https://es.discourse.group/t/proposal-object-unseal-and-object-unfreeze-using-a-secret-symbol/2388/11 "2025-08-14T23:42:15Z")

</div>

I worry this would have security implications. Object.freeze() and Object.seal() are used for ensuring data can’t be tampered with.

---

<div class="post-metadata">

**Author:** ![simonkcleung](https://yyz2.discourse-cdn.com/free1/user_avatar/es.discourse.group/simonkcleung/32/785_2.png) [@simonkcleung](https://es.discourse.group/u/simonkcleung)\
**Post date:** [September 2, 2025, 1:30pm UTC](https://es.discourse.group/t/proposal-object-unseal-and-object-unfreeze-using-a-secret-symbol/2388/12 "2025-09-02T13:30:50Z")

</div>

First of all, as mentioned [here](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperty) -

_“If the old descriptor had its configurable attribute set to false, the property is said to be non-configurable. It is not possible to change any attribute of a non-configurable accessor property, and it is not possible to switch between data and accessor property types.”_

And Object.seal() and Object.freeze() make properties non-configurable, therefore neither the sealed nor frozen object could be reversible.

In other words, you could write a function, say `Lockdata()`, to set all properties non-writable and another one to make all properties revert to writable.
