PureDevTools

JavaScript Proxy & Reflect Reference

All 13 Proxy handler traps and every Reflect method — with invariants, edge cases, and a live code runner

All processing happens in your browser. No data is sent to any server.
TrapTriggered byCategory
getReflect.getObject
setReflect.setObject
hasReflect.hasObject
deletePropertyReflect.deletePropertyObject
applyReflect.applyFunction
constructReflect.constructFunction
getPrototypeOfReflect.getPrototypeOfPrototype
setPrototypeOfReflect.setPrototypeOfPrototype
isExtensibleReflect.isExtensiblePrototype
preventExtensionsReflect.preventExtensionsPrototype
getOwnPropertyDescriptorReflect.getOwnPropertyDescriptorProperty Descriptor
definePropertyReflect.definePropertyProperty Descriptor
ownKeysReflect.ownKeysProperty Descriptor
getObjectES2015
handler.get(target, prop, receiver)

Intercepts property access — `target[prop]`, `target.prop`, or any reflective equivalent. The trap receives the target object, the property key (string or Symbol), and the receiver (the proxy or an object that inherits from the proxy). Return the value to expose for that property.

setObjectES2015
handler.set(target, prop, value, receiver)

Intercepts property assignment — `target[prop] = value`. Should return `true` to indicate success. Returning `false` (or a falsy value) causes a `TypeError` in strict mode. Use `Reflect.set` to forward to the default behaviour.

hasObjectES2015
handler.has(target, prop)

Intercepts the `in` operator — `prop in proxy`. Also triggered by `Reflect.has()`, `with` statement blocks, and some array destructuring checks. Return a boolean indicating whether the property should be considered present.

deletePropertyObjectES2015
handler.deleteProperty(target, prop)

Intercepts the `delete` operator — `delete proxy[prop]`. Also triggered by `Reflect.deleteProperty()`. Return `true` to indicate the property was successfully deleted, `false` to indicate failure (which causes a TypeError in strict mode).

applyFunctionES2015
handler.apply(target, thisArg, argumentsList)

Intercepts function calls — `proxy(args)`, `proxy.call(ctx, args)`, `proxy.apply(ctx, args)`, and `Reflect.apply()`. The target must be a callable (function). Use this to add logging, argument validation, throttling, or memoisation around function calls.

constructFunctionES2015
handler.construct(target, argumentsList, newTarget)

Intercepts `new proxy(args)` and `Reflect.construct()`. The target must be a constructor. The `newTarget` argument is the constructor that was originally called with `new` — useful for implementing abstract base classes and factory patterns. Must return an object.

getPrototypeOfPrototypeES2015
handler.getPrototypeOf(target)

Intercepts `Object.getPrototypeOf(proxy)`, `Object.prototype.__proto__`, `instanceof`, `Reflect.getPrototypeOf()`, and `isPrototypeOf()` checks. Return the prototype to report — an object or null.

setPrototypeOfPrototypeES2015
handler.setPrototypeOf(target, proto)

Intercepts `Object.setPrototypeOf(proxy, proto)` and `Reflect.setPrototypeOf()`. Return `true` to indicate success, `false` to indicate failure (which causes a TypeError in strict mode). Use `Reflect.setPrototypeOf` to forward to default behaviour.

isExtensiblePrototypeES2015
handler.isExtensible(target)

Intercepts `Object.isExtensible(proxy)` and `Reflect.isExtensible()`. The return value must match `Object.isExtensible(target)` exactly — this trap is heavily constrained and is mainly useful for logging or asserting invariants rather than modifying behaviour.

preventExtensionsPrototypeES2015
handler.preventExtensions(target)

Intercepts `Object.preventExtensions(proxy)`, `Object.seal()`, `Object.freeze()`, and `Reflect.preventExtensions()`. Return `true` to indicate success. If you return `true`, then `Object.isExtensible(target)` must also return `false` afterwards — otherwise a TypeError is thrown.

getOwnPropertyDescriptorProperty DescriptorES2015
handler.getOwnPropertyDescriptor(target, prop)

Intercepts `Object.getOwnPropertyDescriptor(proxy, prop)`, `Reflect.getOwnPropertyDescriptor()`, and operations that check own property descriptors (such as `Object.keys`, property spread). Return a property descriptor object or `undefined` (to indicate the property does not exist).

definePropertyProperty DescriptorES2015
handler.defineProperty(target, prop, descriptor)

Intercepts `Object.defineProperty(proxy, prop, descriptor)` and `Reflect.defineProperty()`. Also triggered indirectly by object literals with getters/setters, class field declarations, and property attribute changes. Return `true` on success, `false` on failure.

ownKeysProperty DescriptorES2015
handler.ownKeys(target)

Intercepts `Object.getOwnPropertyNames(proxy)`, `Object.getOwnPropertySymbols(proxy)`, `Object.keys(proxy)`, `Reflect.ownKeys()`, and `for...in` (partially). Return an array of all own property keys (strings and Symbols) that the proxy should appear to have.

You want to validate object properties on assignment — if someone sets user.age = -5, throw an error. You could use a class with setter methods, but then consumers need to call user.setAge(25) instead of user.age = 25. Proxy lets you intercept user.age = 25 directly and validate it, while keeping the natural property syntax. But Proxy has 13 handler traps with specific invariants, and violating an invariant throws a TypeError that’s hard to debug without knowing the rules.

Why This Reference (Not MDN)

MDN documents Proxy traps and Reflect methods on separate pages. This reference covers all 13 traps and their Reflect counterparts side by side — get, set, has, deleteProperty, apply, construct, and 7 more — with invariant rules, edge cases, and live examples on one page.

What Are JavaScript Proxy and Reflect?

Proxy and Reflect are two ES2015 metaprogramming APIs that let you intercept and redefine fundamental operations on JavaScript objects — property access, assignment, function calls, constructor invocations, and more.

Together they enable validation, logging, reactive data binding, virtual properties, access control, and many other patterns — all without modifying the target object itself.

// Basic proxy: log every property access
const obj = { name: "Alice", age: 30 };

const proxy = new Proxy(obj, {
  get(target, prop, receiver) {
    console.log("reading:", prop);
    return Reflect.get(target, prop, receiver);
  },
  set(target, prop, value, receiver) {
    console.log("writing:", prop, "=", value);
    return Reflect.set(target, prop, value, receiver);
  },
});

proxy.name;       // reading: name
proxy.age = 31;   // writing: age = 31

The 13 Proxy Handler Traps

A handler object can define any combination of these 13 trap methods. Traps you omit default to the target’s built-in behaviour.

TrapTriggered byCategory
getobj.prop, obj[prop], Reflect.getObject
setobj.prop = v, Reflect.setObject
hasprop in obj, Reflect.hasObject
deletePropertydelete obj.prop, Reflect.deletePropertyObject
applyfn(), fn.call(), Reflect.applyFunction
constructnew Fn(), Reflect.constructFunction
getPrototypeOfObject.getPrototypeOf, instanceof, Reflect.getPrototypeOfPrototype
setPrototypeOfObject.setPrototypeOf, Reflect.setPrototypeOfPrototype
isExtensibleObject.isExtensible, Reflect.isExtensiblePrototype
preventExtensionsObject.preventExtensions/seal/freeze, Reflect.preventExtensionsPrototype
getOwnPropertyDescriptorObject.getOwnPropertyDescriptor, Reflect.getOwnPropertyDescriptorProperty Descriptor
definePropertyObject.defineProperty, obj.x = v, Reflect.definePropertyProperty Descriptor
ownKeysObject.keys/getOwnPropertyNames/getOwnPropertySymbols, Reflect.ownKeys, for...inProperty Descriptor

Object Traps

get trap

Intercepts property reads: proxy.prop, proxy[prop], Reflect.get(), and prototype-chain lookups.

// Provide default values for missing properties
const withDefaults = new Proxy({}, {
  get(target, prop) {
    return prop in target ? target[prop] : `[${String(prop)} not set]`;
  }
});

withDefaults.name = "Alice";
console.log(withDefaults.name);   // "Alice"
console.log(withDefaults.title);  // "[title not set]"

Critical: for non-writable, non-configurable own data properties, the trap must return the same value as the target — violating this throws a TypeError.

set trap

Intercepts property writes: proxy.prop = value, proxy[prop] = value, Reflect.set().

// Validate and coerce incoming values
const schema = new Proxy({}, {
  set(target, prop, value) {
    if (prop === "age" && (typeof value !== "number" || value < 0)) {
      throw new TypeError("age must be a non-negative number");
    }
    target[prop] = value;
    return true; // must return true to signal success
  }
});

The trap must return true to indicate success. Returning false causes a TypeError in strict mode.

has trap

Intercepts the in operator: prop in proxy, Reflect.has().

// Numeric range check
const range = new Proxy({}, {
  has(target, prop) {
    const n = Number(prop);
    return Number.isFinite(n) && n >= 1 && n <= 100;
  }
});

console.log(50 in range);  // true
console.log(150 in range); // false

deleteProperty trap

Intercepts delete: delete proxy.prop, Reflect.deleteProperty().

const guarded = new Proxy({ id: 1, name: "test" }, {
  deleteProperty(target, prop) {
    if (prop === "id") throw new Error("id is immutable");
    return Reflect.deleteProperty(target, prop);
  }
});

delete guarded.name; // succeeds
delete guarded.id;   // Error: id is immutable

Function Traps

apply trap

Intercepts function calls: fn(), fn.call(), fn.apply(), Reflect.apply(). The target must be callable.

// Throttle a function
function throttle(fn, ms) {
  let lastCall = 0;
  return new Proxy(fn, {
    apply(target, thisArg, args) {
      const now = Date.now();
      if (now - lastCall >= ms) {
        lastCall = now;
        return Reflect.apply(target, thisArg, args);
      }
      console.log("throttled");
    }
  });
}

construct trap

Intercepts new: new Proxy(), Reflect.construct(). Must return an object.

// Abstract class enforcement
class Shape {
  constructor() {
    if (new.target === Shape) {
      throw new Error("Shape is abstract");
    }
  }
}

// Or via Proxy — prevent direct instantiation
const AbstractShape = new Proxy(Shape, {
  construct(target, args, newTarget) {
    if (newTarget === AbstractShape) {
      throw new Error("Cannot instantiate abstract class");
    }
    return Reflect.construct(target, args, newTarget);
  }
});

Prototype Traps

getPrototypeOf trap

Intercepts Object.getPrototypeOf(), instanceof, isPrototypeOf(), and Reflect.getPrototypeOf(). Must return an object or null.

setPrototypeOf trap

Intercepts Object.setPrototypeOf() and Reflect.setPrototypeOf(). Return true on success.

isExtensible trap

Intercepts Object.isExtensible() and Reflect.isExtensible(). Must return the same boolean as Object.isExtensible(target) — the invariant is strictly enforced.

preventExtensions trap

Intercepts Object.preventExtensions(), Object.seal(), Object.freeze(). If you return true, the target must already be non-extensible.


Property Descriptor Traps

getOwnPropertyDescriptor trap

Intercepts Object.getOwnPropertyDescriptor() and Reflect.getOwnPropertyDescriptor(). Return a descriptor object or undefined.

// Report all properties as non-enumerable (hide from for...in and Object.keys)
const hidden = new Proxy({ a: 1, b: 2 }, {
  getOwnPropertyDescriptor(target, prop) {
    const desc = Reflect.getOwnPropertyDescriptor(target, prop);
    return desc ? { ...desc, enumerable: false } : undefined;
  }
});

console.log(Object.keys(hidden));  // [] — none enumerable
console.log(hidden.a);             // 1 — still accessible

defineProperty trap

Intercepts Object.defineProperty() and direct property creation (obj.x = 1). Return true on success.

ownKeys trap

Intercepts Object.keys(), Object.getOwnPropertyNames(), Object.getOwnPropertySymbols(), Reflect.ownKeys(), and for...in. Return an array of strings and/or Symbols.

// Filter keys by prefix
const filtered = new Proxy({ x: 1, _y: 2, z: 3 }, {
  ownKeys(target) {
    return Reflect.ownKeys(target).filter(k => !String(k).startsWith("_"));
  }
});
console.log(Object.keys(filtered)); // ["x","z"]

Reflect: The Default Behaviour API

Every Proxy trap has a corresponding Reflect method that performs the default operation. Using Reflect inside traps is the idiomatic and safe way to forward to the target:

// Always use Reflect inside traps to forward correctly
const proxy = new Proxy(target, {
  get(t, p, r)  { /* intercept, then */ return Reflect.get(t, p, r); },
  set(t, p, v, r) { /* intercept, then */ return Reflect.set(t, p, v, r); },
  has(t, p)     { /* intercept, then */ return Reflect.has(t, p); },
});

Why Use Reflect Instead of Calling the Target Directly?

ApproachIssue
target[prop] in get trapDoesn’t pass receiver → getters see wrong this
target[prop] = value in set trapThrows in strict mode instead of returning false
delete target[prop] in deletePropertyThrows in strict mode instead of returning false
Object.getPrototypeOf(target)Extra overhead and coerces primitives
Reflect.get/set/deleteProperty/…Correct semantics, boolean returns, consistent behaviour

Common Proxy Patterns

Validation Proxy

function validated(target, validators) {
  return new Proxy(target, {
    set(t, prop, value) {
      if (validators[prop]) {
        const error = validators[prop](value);
        if (error) throw new TypeError(`${prop}: ${error}`);
      }
      return Reflect.set(t, prop, value);
    }
  });
}

const user = validated({}, {
  age: (v) => (typeof v !== "number" || v < 0) ? "must be a non-negative number" : null,
  email: (v) => !v.includes("@") ? "must be a valid email" : null,
});

user.age = 25;       // ok
user.email = "alice@example.com"; // ok

Observable / Reactive Data

function observable(target, onChange) {
  return new Proxy(target, {
    set(t, prop, value) {
      const old = t[prop];
      const result = Reflect.set(t, prop, value);
      if (result && old !== value) onChange(prop, old, value);
      return result;
    }
  });
}

const state = observable({ count: 0 }, (prop, old, next) => {
  console.log(`${prop}: ${old} → ${next}`);
});

state.count++;   // count: 0 → 1
state.count++;   // count: 1 → 2

Read-Only (Immutable) Proxy

function readonly(target) {
  const handler = {
    set() { throw new TypeError("This object is read-only"); },
    deleteProperty() { throw new TypeError("This object is read-only"); },
    defineProperty() { throw new TypeError("This object is read-only"); },
  };
  return new Proxy(target, handler);
}

const config = readonly({ host: "localhost", port: 3000 });
console.log(config.host); // "localhost"
config.host = "remote";   // TypeError: This object is read-only

Logging / Debugging Proxy

function logged(target, label = "proxy") {
  return new Proxy(target, {
    get(t, p, r) {
      console.log(`[${label}] get: ${String(p)}`);
      return Reflect.get(t, p, r);
    },
    set(t, p, v, r) {
      console.log(`[${label}] set: ${String(p)} = ${JSON.stringify(v)}`);
      return Reflect.set(t, p, v, r);
    },
  });
}

Invariant Rules Summary

Proxy traps must respect invariants — rules enforced by the JavaScript engine. Violating an invariant always throws a TypeError:

TrapKey Invariant
getNon-writable + non-configurable property must return its actual value
setNon-writable + non-configurable property cannot be assigned a different value
hasCannot return false for a non-configurable own property
deletePropertyCannot return true for a non-configurable own property
getPrototypeOfIf target is non-extensible, must return actual prototype
isExtensibleMust return the same boolean as Object.isExtensible(target) — no exceptions
preventExtensionsCannot return true if target is still extensible
getOwnPropertyDescriptorCannot return undefined for a non-configurable own property
constructMust return an object
ownKeysMust include all non-configurable own keys; no duplicates

Frequently Asked Questions

What is the difference between Proxy and Object.defineProperty?

Object.defineProperty defines a single property with custom attributes (enumerable, configurable, writable, get/set). Proxy intercepts all operations on an object at the meta-level — including property access, assignment, the in operator, function calls, and more. Proxy is more powerful but has slightly higher overhead. Vue 3’s reactivity system switched from Object.defineProperty to Proxy for this reason.

Can Proxies be transparent to the target?

Yes — a proxy with an empty handler (new Proxy(target, {})) is fully transparent. Every operation falls through to the target’s built-in behaviour. Traps are only consulted if defined in the handler.

Is there a performance cost to using Proxy?

Yes, there is a measurable overhead compared to direct property access because each trapped operation goes through an extra function call. In hot loops, this can matter. For general application code — validation, logging, reactive systems — the overhead is negligible.

What is a Revocable Proxy?

Proxy.revocable(target, handler) creates a proxy and a revoke function. Calling revoke() makes the proxy throw a TypeError for any subsequent operation — useful for implementing capability-based security where access can be revoked.

const { proxy, revoke } = Proxy.revocable({ x: 1 }, {});
console.log(proxy.x); // 1
revoke();
console.log(proxy.x); // TypeError: Cannot perform 'get' on a proxy that has been revoked

Can Proxy traps intercept internal slots?

No. Proxy cannot intercept operations that go through internal slots (like Map.prototype.size, Date.prototype.getTime, Promise). These operations require the actual object, not a proxy. Wrapping a Map in a Proxy and forwarding in the get trap works, but operations that check internal slots directly will throw.

When should I use Reflect instead of direct target operations?

Always use Reflect inside Proxy traps. Direct operations (target[prop], target[prop] = value) may lose the receiver (wrong this in getters/setters), throw in strict mode instead of returning a boolean, or behave differently than the standard [[Get]]/[[Set]] operations. Reflect methods are the exact specification-compliant implementations of each fundamental operation.

Related Tools

More JavaScript Tools