eval()
Executes a string as JavaScript code and returns its completion value.
eval(script)- Added in JavaScript ECMAScript 1 (1997)
Parameters
| Parameter | Type | Description |
|---|---|---|
$script | string | TrustedScript | JavaScript 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
evalcan 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
evalcan 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-evaldisables it outright, and Trusted Types makes a string argument aTypeError.
What to use instead
- Parsing data:
JSON.parse(). - Dynamic property access:
obj[key], noteval("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 needsunsafe-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
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.
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); // 1Every 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.
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); // 3new 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.
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