Ch. 1 · JavaScript

JavaScript Proxy Traps for Validation

Validate object writes with Proxy set and get traps, forward with Reflect and avoid the invariants that make a proxy lie about its target.

~3 min readadvancedupdated Oct 4, 2026

A Proxy wraps a target object and intercepts operations on it: property reads, writes, deletes and calls. Validation in a set trap rejects invalid writes at the boundary, but every trap must forward correctly, or the proxy starts contradicting the target and breaks internal invariants.

Before you start

You should know object properties, the Reflect API, and how property attributes work. This article shows read and write interception only; deleteProperty, has, ownKeys and apply follow the same pattern of “decide, then forward”. Proxies validate at runtime, so they do not replace TypeScript types.

Step-by-step walkthrough

Step 1: State the runtime contract

Decide exactly what a valid operation is before writing the trap: which keys exist, which value types are allowed, and whether deletion is permitted. A trap should be a small, total function of the operation, not a place to hide business rules that belong in a domain method.

Step 2: Forward with Reflect

Each trap must perform the real operation and return its result. Use Reflect.set(target, key, value, receiver) and Reflect.get(target, key, receiver) rather than assigning directly: Reflect mirrors the default behavior, respects prototypes and accessors, and keeps the boolean return value correct.

Step 3: Circulate the proxy, never the target

The traps only run for operations on the proxy. If a factory returns the raw target anywhere, callers can bypass validation. Keep the target private to the closure and hand out only the proxy, so there is a single validated doorway.

Worked scenario

Run this with Node.js. Invalid writes throw before the target changes.

function numeric(target = {}) {
  return new Proxy(target, {
    set(object, key, value) {
      if (typeof value !== 'number' || Number.isNaN(value)) {
        throw new TypeError(`${String(key)} must be a finite number`);
      }
      return Reflect.set(object, key, value);
    },
    get(object, key) {
      return Reflect.get(object, key);
    }
  });
}
const point = numeric();
point.x = 3;
console.log(point.x); // 3
try {
  point.y = 'nope';
} catch (error) {
  console.log(error.message); // y must be a finite number
}
JavaScript

Walk through the example

Assigning point.x invokes set, which accepts the number and forwards it. Assigning point.y invokes set again; the type check throws before any write happens, so the target keeps only x. Because the factory returns the proxy, every subsequent read and write goes through the traps.

Common mistake

Returning true from set without writing the value looks harmless but violates Proxy invariants: if the target has a non-writable, non-configurable property, reporting success while ignoring the write throws a TypeError. Similarly, a get trap that returns a different value than a frozen accessor expects will fail. Always let Reflect perform the operation and return its result.

Verify the behavior

Test a valid write, an invalid write, and confirm the target is unchanged after the invalid one is rejected. Check that reading a missing key returns undefined rather than throwing. If you extend the example, test deleteProperty and has, and verify that accessing the proxy through Object.keys and 'in' still behaves consistently with the target.

Interview exercise

You want reads of an unknown key to throw instead of returning undefined. Where does that logic go, and what can a get trap not intercept?

Answer and reasoning

The check belongs in the get trap, using hasOwn or in to distinguish present keys before calling Reflect.get. A proxy cannot intercept direct reads of the whole variable or of values already copied out of the target; it only wraps operations performed through the proxy object itself. That is why the proxy, not the target, must be the object in circulation.

Follow-up discussion

Are proxies free? No: every intercepted operation has extra indirection and can be deoptimized, so keep them at boundaries rather than in hot numeric loops. Do proxies protect against prototype pollution? Only if the set trap rejects __proto__, constructor and prototype keys; a proxy is a mechanism, not a policy.

Continue learning

See how attributes underlie this behavior in property descriptors and prototypes and inheritance. For the complete trap list and invariants, read the MDN Proxy reference and try the JavaScript MCQs.

More in JavaScript

read ✓JavaScript · mid

JavaScript Date and Timezone Pitfalls

Parse and format dates without timezone surprises: zero-based months, date-only versus date-time parsing, and why UTC storage avoids drift.

~3 min readread →
read ✓JavaScript · mid

JavaScript Number Precision and BigInt

Why 0.1 + 0.2 is not 0.3, where safe integer range ends, and when BigInt is the right tool for large identifiers and money.

~2 min readread →
esc