JavaScript Proxy and Revocable Proxy Explained

JavaScript Proxy lets you wrap an object and intercept operations such as reading properties, writing properties, checking for keys, or calling functions. A revocable proxy adds one extra ability: you can later disable the proxy so it no longer works.

Quick answer: Use Proxy when you need to control or observe how an object is used. Use Proxy.revocable() when that control should be temporary and you want a built-in way to turn it off.

Difficulty: Intermediate

You'll understand this better if you know: basic objects, property access with . and [], functions, and how JavaScript returns values from methods.

1. What Is JavaScript Proxy and Revocable Proxy?

A Proxy is a wrapper around another value, usually an object or function. Instead of interacting with the original target directly, code interacts with the proxy, and the proxy can decide how to respond.

  • It can intercept reads, writes, deletions, and other operations.
  • It always works with a target and a handler.
  • The handler contains traps, which are methods for specific operations.
  • A revocable proxy is a proxy that can be disabled later through a revoke function.

This makes proxies useful for validation, logging, access control, virtualization, and advanced metaprogramming.

2. Why Proxy and Revocable Proxy Matter

Proxy matters because it gives you a standard way to customize object behavior without changing every place that uses the object. You can protect invariants, provide lazy behavior, and observe usage in a single place.

Revocable proxy matters when temporary access is important. For example, you may want to expose an object only during a task and then invalidate it after cleanup, reducing the chance of accidental later use.

3. Basic Syntax

Creating a proxy

At minimum, a proxy needs a target and a handler object. If the handler is empty, the proxy behaves like the target.

JavaScript
const target = { name: "Ada" };
JavaScript
const proxy = new Proxy(target, {});

This proxy forwards every operation to target because no traps are defined.

Adding a trap

A trap is a method like get or set that intercepts a particular operation.

JavaScript
const user = { name: "Ada" };
JavaScript
const proxy = new Proxy(user, {
JavaScript
  get(target, property, receiver) {
JavaScript
    return Reflect.get(target, property, receiver);
JavaScript
  }
JavaScript
});

Using Reflect inside a trap is a common pattern because it performs the default operation safely and predictably.

Creating a revocable proxy

Proxy.revocable() returns an object with two properties: proxy and revoke.

JavaScript
const { proxy, revoke } = Proxy.revocable({ id: 1 }, {});
JavaScript
console.log(proxy.id);
Output✓ Tested · Node 24
Uncaught ReferenceError: proxy is not defined
JavaScript
revoke();

After revocation, any attempt to use proxy throws a TypeError.

4. Step-by-Step Examples

Example 1: Logging property reads

This proxy prints whenever code reads a property. It is helpful for debugging and understanding object access patterns.

JavaScript
const settings = { theme: "dark", fontSize: 16 };
JavaScript
const loggedSettings = new Proxy(settings, {
JavaScript
  get(target, property, receiver) {
JavaScript
    console.log("Reading", property);
Output✓ Tested · Node 24
Uncaught ReferenceError: property is not defined
JavaScript
    return Reflect.get(target, property, receiver);
JavaScript
  }
JavaScript
});
JavaScript
console.log(loggedSettings.theme);
Output✓ Tested · Node 24
Uncaught ReferenceError: loggedSettings is not defined

This shows how a proxy can observe access without changing the data itself.

Example 2: Validating writes

Proxies are often used to validate values before they are stored. Here, the proxy rejects invalid ages.

JavaScript
const person = { age: 20 };
JavaScript
const validatedPerson = new Proxy(person, {
JavaScript
  set(target, property, value, receiver) {
JavaScript
    if (property === "age" && (value < 0 || value > 130)) {
JavaScript
      throw new RangeError("age must be between 0 and 130");
JavaScript
    }
JavaScript
    return Reflect.set(target, property, value, receiver);
JavaScript
  }
JavaScript
});
JavaScript
validatedPerson.age = 25;

Here the set trap enforces a rule before the write reaches the target.

Example 3: Hiding private fields by convention

JavaScript does not have true private object keys in plain objects, but a proxy can hide properties that begin with an underscore.

JavaScript
const account = { name: "Lin", _token: "secret" };
JavaScript
const safeAccount = new Proxy(account, {
JavaScript
  get(target, property, receiver) {
JavaScript
    if (String(property).startsWith("_")) {
JavaScript
      return undefined;
JavaScript
    }
JavaScript
    return Reflect.get(target, property, );
JavaScript
  }
JavaScript
});
JavaScript
console.log(safeAccount.name);
Output✓ Tested · Node 24
Uncaught ReferenceError: safeAccount is not defined
JavaScript
console.log(safeAccount._token);
Output✓ Tested · Node 24
Uncaught ReferenceError: safeAccount is not defined

This pattern is useful for UI-facing wrappers, but it is not a security boundary by itself.

Example 4: Temporarily exposing an object

Revocable proxies are useful when code should only use an object during a limited period. After cleanup, the proxy is disabled.

JavaScript
const { proxy: session, revoke } = Proxy.revocable({ userId: 42 }, {});
JavaScript
console.log(session.userId);
Output✓ Tested · Node 24
Uncaught ReferenceError: session is not defined
JavaScript
revoke();

After revoke(), the proxy can no longer be used for reads, writes, or any other operation.

5. Practical Use Cases

  • Validating form-like data before it is stored in a state object.
  • Building debugging tools that record property access and mutation.
  • Implementing transparent memoization or lazy-loading behavior.
  • Creating view models that present a safer or simpler interface than the underlying data.
  • Protecting capability-based APIs by revoking access after a task completes.
  • Supporting advanced libraries that need to observe object behavior without changing call sites.

6. Common Mistakes

Mistake 1: Forgetting to return a value from a trap

A trap must usually return something meaningful. If a get trap returns undefined by accident, property reads appear broken.

Problem: The trap logs access but never forwards the original value, so every property lookup returns undefined.

JavaScript
const user = { name: "Ada" };
JavaScript
const proxy = new Proxy(user, {
JavaScript
  get(target, property) {
JavaScript
    console.log(property);
Output✓ Tested · Node 24
Uncaught ReferenceError: property is not defined
JavaScript
  }
JavaScript
});

Fix: Return the default result with Reflect.get() or a custom value only when you really want to change behavior.

JavaScript
const fixedProxy = new Proxy(user, {
JavaScript
  get(target, property, receiver) {
JavaScript
    console.log(property);
Output✓ Tested · Node 24
Uncaught ReferenceError: property is not defined
JavaScript
    return Reflect.get(target, property, );
JavaScript
  }
JavaScript
});

The corrected version works because it preserves the target’s normal behavior while still observing access.

Mistake 2: Returning false from a set trap when assignment should succeed

If a set trap returns false, JavaScript treats the assignment as failed. In strict mode, this can become a runtime error.

Problem: The trap blocks every write, so even valid updates fail and can throw a TypeError in strict code.

JavaScript
"use strict";
JavaScript
const target = { count: 0 };
JavaScript
const proxy = new Proxy(target, {
JavaScript
  set() {
JavaScript
    return false;
JavaScript
  }
JavaScript
});
JavaScript
proxy.count = 1;

Fix: Forward valid assignments and only block the ones that violate your rule.

JavaScript
const safeProxy = new Proxy(target, {
JavaScript
  set(target, property, value, ) {
JavaScript
    if (property === "count" && value < 0) {
JavaScript
      return false;
JavaScript
    }
JavaScript
    return Reflect.set(target, property, value, );
JavaScript
  }
JavaScript
});

The corrected version works because it only rejects invalid values and allows normal writes through.

Mistake 3: Using a proxy after revocation

Revocable proxies stop working once revoke() is called. Any later access throws immediately.

Problem: The code keeps a reference to the proxy and tries to use it after revocation, which causes a TypeError such as Cannot perform 'get' on a proxy that has been revoked.

JavaScript
const { proxy, revoke } = Proxy.revocable({ name: "Ada" }, {});
JavaScript
revoke();
JavaScript
console.log(proxy.name);
Output✓ Tested · Node 24
Uncaught ReferenceError: proxy is not defined

Fix: Revoke only after you are completely finished with the proxy, and remove or replace any remaining references.

JavaScript
const { proxy: session, revoke: disableSession } = Proxy.revocable({ name: "Ada" }, {});
JavaScript
console.log(session.name);
Output✓ Tested · Node 24
Uncaught ReferenceError: session is not defined
JavaScript
disableSession();

The fixed version works because the proxy is used only before it is intentionally disabled.

7. Best Practices

Practice 1: Prefer Reflect for default behavior

Reflect keeps traps small and preserves normal JavaScript semantics, which reduces accidental bugs.

JavaScript
const proxy = new Proxy({ count: 1 }, {
JavaScript
  get(target, property, ) {
JavaScript
    return Reflect.get(target, property, );
JavaScript
  }
JavaScript
});

This is better than manually indexing into the target because it respects getters and inheritance correctly.

Practice 2: Keep proxy logic focused

One proxy should usually do one job, such as validation or logging. A large handler becomes hard to test and reason about.

JavaScript
const priceProxy = new Proxy({ price: 10 }, {
JavaScript
  set(target, property, value, ) {
JavaScript
    if (property === "price" && value < 0) {
JavaScript
      throw new RangeError("price cannot be negative");
JavaScript
    }
JavaScript
    return Reflect.set(target, property, value, );
JavaScript
  }
JavaScript
});

This is easier to maintain than mixing validation, logging, and authorization in one handler.

Practice 3: Be careful when proxying objects with methods

Methods can rely on the correct this value. Using the trap arguments correctly helps avoid surprising behavior.

JavaScript
const counter = {
JavaScript
  count: 0,
JavaScript
  increment() {
JavaScript
    return this.count += 1;
JavaScript
  }
JavaScript
};
JavaScript
const proxy = new Proxy(counter, {
JavaScript
  get(target, property, ) {
JavaScript
    return Reflect.get(target, property, );
JavaScript
  }
JavaScript
});

Passing the receiver into Reflect.get() helps methods and getters see the proxy as expected.

8. Limitations and Edge Cases

  • Proxies only intercept operations that go through the proxy. Direct access to the original target is not intercepted.
  • They cannot change private class fields because private field access is not mediated through proxy traps.
  • Some built-in objects and exotic behaviors can be tricky to proxy correctly, especially when invariants must be preserved.
  • Proxies can add overhead, so they are not ideal for hot code paths that run millions of times.
  • Revoked proxies are permanently disabled; there is no undo operation.
  • Proxy traps must obey JavaScript invariants, or the engine may throw errors if the proxy lies about non-configurable or non-writable properties.

Warning: Do not treat a proxy as a security boundary for secrets stored in the same process. If code can still reach the original target, it can bypass the proxy completely.

9. Practical Mini Project

In this mini project, we will build a small settings guard that validates updates and can be disabled after the settings screen closes.

The proxy will:

  • allow only known settings keys,
  • validate values before storing them,
  • and be revocable when the settings session ends.
JavaScript
function createSettingsSession(initialSettings) {
JavaScript
  const allowedKeys = ["theme", "fontSize", "notifications"];
JavaScript
  const guarded = Proxy.revocable(initialSettings, {
JavaScript
    set(target, property, value, ) {
JavaScript
      if (!allowedKeys.includes(String(property))) {
JavaScript
        throw new ReferenceError("Unknown setting: " + String(property));
JavaScript
      }
JavaScript
      if (property === "fontSize" && (value < 10 || value > 32)) {
JavaScript
        throw new RangeError("fontSize must be between 10 and 32");
JavaScript
      }
JavaScript
      return Reflect.set(target, property, value, );
JavaScript
    }
JavaScript
  });
JavaScript
  return {
JavaScript
    settings: guarded.proxy,
JavaScript
    close() {
JavaScript
      guarded.revoke();
JavaScript
    }
JavaScript
  };
JavaScript
}
JavaScript
const { settings, close } = createSettingsSession({
JavaScript
  theme: "dark",
JavaScript
  fontSize: 16,
JavaScript
  notifications: true
JavaScript
});
JavaScript
settings.fontSize = 18;
JavaScript
close();

This example combines validation with temporary access control, which is a realistic reason to use a revocable proxy.

10. Key Points

  • Proxy wraps a target and intercepts operations through traps.
  • Reflect is the safest way to forward default behavior inside traps.
  • Proxy.revocable() returns a proxy plus a function that permanently disables it.
  • Proxies are useful for validation, logging, and behavior customization, but they add complexity.
  • Revoked proxies throw a TypeError if used after revocation.
  • Proxy logic should stay small and focused so that behavior is predictable.

11. Practice Exercise

Create a proxy that wraps a profile object with these rules:

  • Reading fullName should return the first and last name joined with a space.
  • Writing to age should allow only whole numbers from 0 to 120.
  • Any attempt to access password should return undefined.
  • Use Proxy.revocable() so the proxy can be disabled after use.

Expected output: A working proxy that formats fullName, validates age, hides password, and throws after revocation.

Hint: Use get for reads, set for writes, and Reflect to forward allowed operations.

JavaScript
function createProfileProxy(profile) {
JavaScript
  const guarded = Proxy.revocable(profile, {
JavaScript
    get(target, property, ) {
JavaScript
      if (property === "password") {
JavaScript
        return undefined;
JavaScript
      }
JavaScript
      if (property === "fullName") {
JavaScript
        return target.firstName + " " + target.lastName;
JavaScript
      }
JavaScript
      return Reflect.get(target, property, );
JavaScript
    },
JavaScript
    set(target, property, value, ) {
JavaScript
      if (property === "age") {
JavaScript
        if (!Number.isInteger(value) || value < 0 || value > 120) {
JavaScript
          throw new RangeError("age must be an integer between 0 and 120");
JavaScript
        }
JavaScript
      }
JavaScript
      return Reflect.set(target, property, value, );
JavaScript
    }
JavaScript
  });
JavaScript
  return guarded;
JavaScript
}
JavaScript
const profile = createProfileProxy({
JavaScript
  firstName: "Ada",
JavaScript
  lastName: "Lovelace",
JavaScript
  age: 36,
JavaScript
  password: "secret"
JavaScript
});
JavaScript
console.log(profile.fullName);
Output✓ Tested · Node 24
Uncaught ReferenceError: profile is not defined
JavaScript
profile.age = 40;
Show solution
JavaScript
console.log(profile.password);
Output✓ Tested · Node 24
Uncaught ReferenceError: profile is not defined

This solution works because each trap handles exactly one rule and forwards safe operations with Reflect.

12. Final Summary

JavaScript Proxy gives you a powerful way to intercept operations on objects and functions. You can use it to log access, validate input, customize lookup behavior, and create advanced abstractions that are hard to build with plain objects alone.

Proxy.revocable() adds a lifecycle control layer. It is especially useful when an object should only be available temporarily, because revocation permanently disables the proxy and helps prevent accidental use after a task ends.

If you are learning proxies for the first time, start with get and set, then practice forwarding behavior with Reflect. After that, explore other traps and build small wrappers around real objects so the rules feel practical rather than abstract.