eval()

Executes a string as JavaScript code and returns its completion value.

Signature
eval(script)
  • Added in JavaScript ECMAScript 1 (1997)

Parameters

ParameterTypeDescription
$scriptstring | TrustedScriptJavaScript source to evaluate — an expression, a statement, or a sequence of statements. It is parsed as a script, so import declarations are not allowed.

Return value

anyThe completion value of the evaluated code, or undefined if that value is empty. If the argument is not a string primitive or a TrustedScript, it is returned unchanged.

Errors

Throws SyntaxError if the source cannot be parsed, TypeError if a string is passed while Trusted Types are enforced with no default policy, and propagates any exception thrown by the evaluated code.

Changelog

  • PHP ECMAScript 5In strict mode, var and function declarations inside eval no longer leak into the surrounding scope, and eval may not be used as a binding name.
  • PHP Trusted TypesWhen a CSP enforces Trusted Types, passing a plain string throws TypeError unless a default policy is defined.

eval() compiles and runs a string as JavaScript in the current realm. It works, and it is almost always the wrong tool.

Why it is avoided

  • Injection. The evaluated code runs with the caller's full privileges. Any untrusted fragment inside that string is arbitrary code execution — the textbook XSS vector.
  • Performance. A direct eval can see the enclosing scope, so engines must deoptimise the containing function and abandon optimisations they would otherwise make.
  • Scope leakage. In sloppy mode a direct eval can create and rewrite variables in the calling scope, which breaks every assumption a reader or a bundler makes.
  • It is blocked in practice. A Content Security Policy without unsafe-eval disables it outright, and Trusted Types makes a string argument a TypeError.

What to use instead

  • Parsing data: JSON.parse().
  • Dynamic property access: obj[key], not eval("obj." + key).
  • Choosing behaviour at runtime: a lookup object of functions.
  • Loading code at runtime: dynamic import().
  • Genuinely generating code: new Function(...), which at least cannot see local scope — still needs unsafe-eval.

Direct versus indirect

eval(src) is a direct call and runs in local scope. (0, eval)(src) or eval?.(src) is indirect and runs in global scope with no access to locals — the lesser evil when you must evaluate something.

Example

Try it yourself
Loading editor…
Press Run to execute the code.

When to use it

  • Implementing a REPL or a developer console where running arbitrary source is the feature.
  • Evaluating an expression in a debugging tool against a captured scope.
  • Bootstrapping a code-generation step in a build tool, where the input is code you produced.

Frequently asked questions

Why is eval considered evil?

Because it executes whatever string it is given with the caller's privileges, so any untrusted input in that string is arbitrary code execution. It also blocks engine optimisations in the surrounding function and is disabled outright by a Content Security Policy that omits unsafe-eval.

What should I use instead of eval?

JSON.parse for data, bracket notation for dynamic property access, a lookup object for dynamic dispatch, and dynamic import() for loading code. If you truly must compile a string, new Function is safer because it cannot see local variables.

What is the difference between direct and indirect eval?

A direct call written literally as eval(...) runs in the calling scope and can read and write its local variables; an indirect call such as (0, eval)(...) or eval?.(...) runs in global scope with no access to locals. Indirect eval also only runs in strict mode if the source string itself carries a "use strict" directive.

More examples

Direct versus indirect eval

The direct call is evaluated inside the function's scope and can read `secret`; both indirect forms run as global code and cannot. That isolation is why the indirect form is preferred when evaluation is unavoidable.

Example · javascript
function scopes() {
  const secret = "local";
  console.log(eval("typeof secret"));      // "string" — direct, sees locals
  console.log(eval?.("typeof secret"));    // "undefined" — indirect, global scope
  console.log((0, eval)("typeof secret")); // "undefined"
}
scopes();

// Indirect eval writes to the global scope
(0, eval)("var fromIndirect = 1;");
console.log(globalThis.fromIndirect);      // 1

Every common use has a better answer

Path lookup becomes a reduce, dynamic dispatch becomes an object lookup that also rejects unknown names, and data parsing becomes JSON.parse. None of the three can execute attacker-supplied code.

Example · javascript
const data = { user: { name: "Ada" } };
const path = "user.name";

// eval("data." + path) — don't
console.log(path.split(".").reduce((o, k) => o?.[k], data)); // Ada

const ops = { add: (a, b) => a + b, mul: (a, b) => a * b };
const op = "mul";
console.log(ops[op]?.(3, 4) ?? "unknown op");                 // 12

console.log(JSON.parse('[1,2,3]').length);                    // 3

new Function as the constrained alternative

new Function compiles in global scope, so it cannot capture locals the way direct eval does — a real reduction in blast radius. It is still dynamic compilation, so a strict Content Security Policy rejects it alongside eval.

Example · javascript
const local = "invisible";
const f = new Function("a", "b", "return a + b;");
console.log(f(2, 3)); // 5

const g = new Function("return typeof local;");
console.log(g());     // "undefined" — no access to local scope

// Still compiles a string, so CSP without unsafe-eval blocks both
console.log(typeof Function.prototype.constructor);

Discussion

  • Be the first to comment on this lesson.