eval & new Function
Learn what eval and new Function do with code strings, how direct and indirect eval choose scope, why CSP can block them, and safer patterns to reach for instead.
- 01Tell the three apartSay which variables direct eval, indirect eval, and new Function can see.
- 02Spot the dangerExplain why running text as code is a security risk, and how CSP blocks it.
- 03Choose safer toolsUse JSON.parse, lookup tables, and small parsers instead of eval.
A string that runs as code
Most JavaScript comes from a file or a <script> tag. The browser reads that code before it runs it.
eval and new Function work differently. They take a string, which is just text, while the program is already running. Then they ask the JavaScript engine to read that text as code and run it. The engine is the part of the browser that runs JavaScript.
Running text as code changes some rules. It changes which variables the code can see. This is called its scope. It also affects closures, strict mode, browser security, and speed. This lesson shows what really happens. Then it shows how to do the same jobs without code strings, using plain data and functions you already wrote.
Be very careful with code strings. If you only need data, read it as data. If you need to pick an action, pick it from functions you already wrote.
A stranger hands you a note and says, "Do whatever this says." If you read it in your room, the note can use everything in the room. If you read the same note in the lobby of the building, it can only use what everyone in the lobby can see.
- In real life: The room you are in
- In JavaScript: The function that is running
- In real life: A note that says what to do
- In JavaScript: The string you pass to
eval - In real life: Reading the note inside the room
- In JavaScript: Direct
eval(...)can see local variables - In real life: Reading the note in the lobby
- In JavaScript: Indirect eval sees only global names
Direct vs indirect eval
STEP THROUGHThere are two kinds of eval call:
- Direct eval is a call written exactly as
eval(...), where the nameevalstill means JavaScript's built-in eval. The string runs inside the function that made the call, so it can read that function's local variables. - Indirect eval is any other way to call the same built-in eval. The string runs as global code, so it sees only global names. Global names are the ones every script on the page can see.
Each of these calls is indirect:
- a second name for eval, like
const indirect = eval, thenindirect(...) - the comma trick
(0, eval)(...) - the optional call
eval?.(...) - a property read like
globalThis.eval(...)
globalThis.name = "global name";function readName() { const name = "local name"; const indirect = eval; return [eval("name"), indirect("name"), (0, eval)("name")];}console.log(readName().join(" | "));Line 1 puts a global name on globalThis, the object that holds global names. Line 3 makes a local name inside readName. Line 4 makes indirect, a second name for eval.
Line 5 calls eval three ways. The program prints local name | global name | global name. Only the first call, written exactly as eval(...), finds the local name.
eval("name") looks inside readName and finds the local name. The indirect call sees only global names, so it finds "global name".function chooseRunner() { const eval = (text) => "not JavaScript: " + text; return eval("1 + 1");}console.log(chooseRunner());Line 2 makes a local function that is also named eval. It only joins two strings. Line 3 calls that local function, not the built-in eval, so the program prints not JavaScript: 1 + 1.
So direct eval needs two things: the call is written eval(...), and the name eval means the built-in eval. This example only runs in sloppy mode, the older mode without "use strict". Strict mode does not allow a variable named eval.
Pick a call shape. Guess where x comes from, then step through. This replays the example code; it is not a look inside the engine.
script
function readX() { const x = "local x"; return eval("x");}(0, eval)("x") looks odd, but people write it on purpose to get indirect eval. The comma runs both sides and gives back the right side: the eval function itself. The call then uses that function value, not the name eval, so it is not direct.
The lab below runs four fixed snippets inside a sandboxed iframe. An iframe is a small page inside this page. Sandboxed means it is kept apart, so its code can't touch this lesson. Press a button to run one snippet and see the real result.
const cases = { direct: 'function read(){ const x = "local x"; return eval("x"); } read();', indirect: 'globalThis.x = "global x"; function read(){ const x = "local x"; return (0, eval)("x"); } read();', sloppyVar: 'function read(){ eval("var leaked = 1"); return typeof leaked; } read();', strictVar: 'function read(){ "use strict"; eval("var hidden = 1"); return typeof hidden; } read();'};addEventListener("message", (event) => { if (!event.data || event.data.lesson !== "eval" || !(event.data.caseId in cases)) return; try { const value = eval(cases[event.data.caseId]); parent.postMessage({ lesson: "eval", caseId: event.data.caseId, status: "ok", value: String(value) }, "*"); } catch (error) { parent.postMessage({ lesson: "eval", caseId: event.data.caseId, status: "error", value: error.name + ": " + error.message }, "*"); }});Preparing the sandboxed frame…
choose a snippetThis page sends only the name of a snippet. The iframe holds the four fixed snippets, runs the one you chose, and sends back the result. This page checks that the reply came from that iframe. You can't type code into this lab.
A var made inside eval
BROWSER LABDirect eval can also make new variables. What happens to a var made inside it depends on the mode:
- In sloppy mode, the
varis added to the function that called eval. It leaks out. - In strict mode, direct eval keeps its own set of variables. The
varis gone when eval ends.
function sloppy() { eval("var leaked = 1"); return typeof leaked;}function strict() { "use strict"; eval("var hidden = 1"); return typeof hidden;}typeof gives the type of a value as a string. For a name that doesn't exist, it gives "undefined" instead of an error.
In the lab above, the sloppy case gives "number": leaked still exists after eval, so line 3 finds it. The strict case gives "undefined": hidden stayed inside eval, so line 8 can't find it.
function readVar() { "use strict"; eval("var inside = 1"); return typeof inside;}console.log(readVar());Line 2 turns on strict mode for the function. Line 3 makes inside, but only inside eval. Line 4 can't see it, so line 6 prints undefined.
Strict mode makes eval a little tidier. It does not make running a code string safe.
eval only treats strings as code. Give it anything else and you get the same value back. eval(42) returns 42, and eval(object) returns that same object.
new Function makes a function that sees only global names
INTERACTIVEnew Function builds a new function from strings. Every string except the last one is a parameter name. The last string is the body: the code inside the function. For example, new Function("a", "b", "return a + b") makes a function that adds a and b.
The machine stands in the lobby. It makes a snack from its recipe and the buttons you press. It can use anything in the lobby. It can't reach into the locked offices upstairs, even if you came down from one of them to use it.
- In real life: Buttons you press
- In JavaScript: Parameter names like
aandb - In real life: The recipe the machine follows
- In JavaScript: The function body string
- In real life: The building lobby
- In JavaScript: Global scope
- In real life: Locked offices upstairs
- In JavaScript: Local variables it can't see
globalThis.shared = 7;const add = new Function("a", "b", "return a + b");function makeSecretReader() { const secret = 10; return new Function("return secret");}const readSecret = makeSecretReader();const readShared = new Function("return shared");console.log(add(2, 3));try { console.log(readSecret()); }catch (error) { console.log(error.name); }console.log(readShared());Line by line:
- Line 2 makes
addwith two parameters, so line 9 prints5. - Lines 3 to 6 make a local
secretand build a function that tries to read it. That function can't seesecret. So line 10 throws, and line 11 printsReferenceError, the error for a name that doesn't exist. - Line 8 builds a function that reads the global
shared, so line 12 prints7.
new Function sees only global names. So readShared() finds shared, but readSecret() can't see the local secret and throws.const cases = { add: () => new Function("a", "b", "return a + b")(2, 3), closure: () => { const secret = 10; return new Function("return secret")(); }, global: () => { globalThis.shared = 7; return new Function("return shared")(); }};addEventListener("message", (event) => { if (!event.data || event.data.lesson !== "eval-new-function" || !(event.data.caseId in cases)) return; try { parent.postMessage({ lesson: "eval-new-function", caseId: event.data.caseId, status: "ok", value: String(cases[event.data.caseId]()) }, "*"); } catch (error) { parent.postMessage({ lesson: "eval-new-function", caseId: event.data.caseId, status: "error", value: error.name + ": " + error.message }, "*"); }});Preparing the sandboxed frame…
choose a caseA function made by new Function can use its parameters and global names. It can't see the local secret in the code that made it.
const sloppy = new Function("return typeof this");const strict = new Function('"use strict"; return typeof this');console.log(sloppy());console.log(strict());Both functions are made from strings. Inside a function, this is the object the function is working on. A sloppy function called on its own gets the global object as this, so line 3 prints object. The second body starts with "use strict", so its this stays undefined and line 4 prints undefined.
So a body made by new Function is sloppy unless the string itself starts with "use strict". Being inside a strict file does not change that. Like eval, new Function can be blocked by a browser rule called CSP. The next section explains it.
Security and Content Security Policy
BROWSER LABThe biggest problem with eval is security. Suppose some text from a visitor ends up inside an eval string. Now the visitor isn't just giving you a value. They are writing code that runs on your page, with everything your page can reach. This attack is called code injection.
Browsers have a guard against it: a Content Security Policy, or CSP. A CSP is a list of rules that a site sends with its page. The script-src rule says which scripts may run. Unless that rule includes unsafe-eval, the browser refuses to turn strings into code. Many real sites leave unsafe-eval out.
A CSP without unsafe-eval is like a building rule that says, "Nobody follows instructions from notes." The guard doesn't read the note first. Friendly notes and harmful notes are both stopped.
- In real life: A rule posted at the front desk
- In JavaScript: Content Security Policy
- In real life: "Nobody follows notes"
- In JavaScript: A policy without
unsafe-eval - In real life: A guard stops anyone reading a note
- In JavaScript: The browser throws
EvalError
eval("21 * 2"). With unsafe-eval in the policy it gives 42; without it, the browser throws an EvalError.<meta http-equiv="Content-Security-Policy" content="script-src 'unsafe-inline'"><script>try { const value = eval("21 * 2"); parent.postMessage({ lesson: "eval-csp", status: "ok", value: String(value) }, "*");} catch (error) { parent.postMessage({ lesson: "eval-csp", status: "error", value: error.name + ": " + error.message }, "*");}</script>Preparing the sandboxed frame…
waitingwaitingBoth frames let their own inline script start. Only the frame whose policy includes unsafe-eval can turn the eval string into code. In Chrome, the other frame throws an EvalError.
In Chrome, the blocked frame throws an EvalError. The message after the error name is different in each browser, so code should not depend on it. The same rule also blocks:
new FunctionsetTimeoutorsetIntervalcalled with a string instead of a function
let message = "waiting";const update = () => { message = "ready"; };const runLater = (callback) => callback();runLater(update);console.log(message);Line 2 stores a function in update, not a string of code. Line 3 makes runLater, a stand-in for a timer: it takes a function and calls it. Line 4 hands it update, so no text has to become code. Line 5 prints ready.
With a real timer, write setTimeout(update, 1000), not setTimeout("update()", 1000).
Safer ways to do the same job
SORTERMost of the time, people reach for eval to do one of three ordinary jobs. None of them needs a code string:
- read data, like JSON text from a server
- pick one action from a set of known actions
- turn text, like a page number, into a simple value
const data = JSON.parse('{"score":42}');const formatters = { upper: (text) => text.toUpperCase() };const key = "upper";const page = new URLSearchParams("?page=2");console.log(data.score);console.log(formatters[key]("safe"));console.log(Number(page.get("page")));Line 1 reads JSON text as data. Lines 2 and 3 keep a function in an object and pick it with a key. Line 4 reads a URL query string, the part after ?. The last three lines print 42, SAFE, and 2.
- For data from a server or from storage, use
JSON.parse. JSON is a text format for data. - To pick known behavior with a key, use an object, a
Map, or aswitchstatement. - For URL or form values, read them with
URLSearchParams, turn text into a number withNumber(), and check that the value makes sense.
const actions = { greet: (user) => "Hello, " + user, count: (cart) => String(cart.length),};const action = "greet";console.log(actions[action]("Asha"));Lines 1 to 4 list the only actions the page supports. An object used like this is called a lookup table: you look up a key and get back a value. Line 5 holds the chosen action as a plain string. Line 6 looks up that key, calls the function it finds, and prints Hello, Asha.
If the key isn't in the table, you can stop before anything runs.
"greet" picks one function from the actions table. Only functions already in the table can run, so no text becomes code.const rawPage = new URLSearchParams("?page=oops").get("page");const page = Number(rawPage);console.log(Number.isInteger(page) && page > 0 ? page : 1);Line 1 reads the text "oops" from the URL. Line 2 tries to turn it into a number and gets NaN, which means "not a number". Line 3 checks for a whole number above 0. NaN fails that check, so line 3 prints 1, the first page.
The check shows clearly which input the page accepts.
function addTwoNumbers(text) { const parts = text.split("+").map((part) => part.trim()); if (parts.length !== 2 || parts.some((part) => part === "" || Number.isNaN(Number(part)))) { throw new Error("Use two numbers separated by +"); } return Number(parts[0]) + Number(parts[1]);}console.log(addTwoNumbers("2 + 5"));A parser is code that reads text and checks that it follows exact rules. This one accepts exactly two numbers with a + between them.
- Line 2 splits the text at
+and trims the spaces from each part. - Line 3 checks for exactly two parts that are both numbers. If not, line 4 throws an error.
- Line 6 adds the two numbers, so the last line prints
7.
Text like alert(1) is rejected. It never runs, and it can't reach the page's variables.
- Turn text from a server like
{"ok":true}into data - Read
user[name]when the key comes from a menu - Let visitors type any formula into a calculator
- A lesson iframe runs one of three fixed snippets
- A trusted build tool makes a small helper function from text
- Read
?page=2and turn it into a number
Sort each situation by the safest way to handle it.
The speed cost of eval
Engines make code fast by studying it before it runs. For example, they work out which variables each function reads and writes.
Direct eval spoils that study. The string might read any local name, or make a new var in sloppy mode. So the engine has to play safe and skip some speed-ups for that function.
There is also extra work while the page runs. Normal code is read once, when the script loads. An eval string, or a new Function body, is read and turned into runnable code when that line runs. How long that takes depends on the browser, the device, and the code, so this lesson gives no single number.
Running the same eval in a loop repeats the risk, and it may repeat that work too. A normal function, a lookup table, or a small parser has a fixed shape that the engine can study. It also lets you check input before you act on it. Measure speed with real work only after you have the safer design.
Don't remove eval just because a tiny timing test said it was slower. Remove it because normal functions and data are clearer, safer, easier for the engine to speed up, and allowed by strict CSPs.
Where you will use this
You will rarely write eval in your own app. But you will review code that uses it. Look for code strings during security reviews, when a site adds a CSP, and when a bug comes from a variable that seems to appear from nowhere.
Each row below is one way to run text as code, or a safer tool. Read across a row to see what it can reach and what it is for.
| What you write | Which variables can it see? | Strict or sloppy? | What is it good for? |
|---|---|---|---|
Direct eval(code) | It sees the variables of the function that called it, and every scope around it. | It follows the calling code. In strict code, its own vars stay inside. | It is the most powerful and the most dangerous. |
| Indirect eval | It sees only global names. | The string decides, with its own "use strict". | It runs code that can't see local variables. |
new Function(...) | It sees only global names and its own parameters. | It is sloppy unless the body starts with "use strict". | It makes a function you can call again and again. |
JSON.parse, a lookup table, or a parser | It sees only the values you pass in. | It is normal code. | Use these for data and for picking actions. |
If you build a teaching tool, a rules engine, or a formula box, write a small parser for just the language you need. If you truly must run JavaScript, keep it apart from the page, as in the Windows, iframes & postMessage lesson:
- run it in a sandboxed iframe
- run only fixed or trusted code
- stop it if it runs too long, where you can
- check every message it sends back
A parser is not the same as eval. It accepts only the text your feature needs, like numbers and + for a tiny calculator. It gives back a value and never gets access to the page's variables. That makes it easier to check for safety, and it can give clear error messages.
Common misconceptions
“Every eval call is direct eval.”
No. Only a call written exactly as eval(...) is direct. A second name for eval, or (0, eval)(...), makes it indirect.
“new Function is a safer eval that sees my variables.”
No. It can't see local variables at all, only global names. It still turns strings into code, and CSP still blocks it.
“JSON is JavaScript, so eval is fine for it.”
No. JSON is data. JSON.parse rejects anything that isn't data, but eval runs whatever code it gets.
“CSP only blocks harmful strings.”
No. CSP can't tell what a string is for. Without unsafe-eval, even a friendly eval string fails.
“Short eval strings cost nothing.”
No. The engine still has to read them while the page runs, and it skips some speed-ups in the function that uses eval.
Practice exercises
6 EXERCISESType exactly what the console prints.
function demo() {
const x = "nearby";
return eval("x");
}
console.log(demo());This is direct eval, so it can read the local x inside demo. It prints nearby.
Type exactly what the console prints.
globalThis.x = "global";
function demo() {
const x = "nearby";
return (0, eval)("x");
}
console.log(demo());Indirect eval skips the local x and reads the global x on globalThis. It prints global.
Guess the true or false it prints.
const value = { ok: true };
console.log(eval(value) === value);eval gives the object back unchanged. Both sides are the same object, so it prints true.
Guess the number it prints.
globalThis.tax = 5;
function make() {
const tax = 99;
return new Function("price", "return price + tax");
}
console.log(make()(10));The new function sees the global tax, which is 5. So 10 + 5 prints 15.
The data arrives as JSON text. Which method should you use to read it?
const user = JSON.parse('{"name":"Ada","admin":false}');
console.log(user.name + ":" + user.admin);const user = JSON.parse(text);Use JSON.parse for JSON text. Then read the properties of the object it gives back, like user.name.
A dashboard gets a panel name from a menu. Name the safe pattern this starter code uses instead of building a string for eval.
const panels = {
profile: (user) => "Profile: " + user,
orders: (user) => "Orders for " + user,
};
const requestedPanel = "profile";
const render = panels[requestedPanel];
console.log(render("Asha"));const render = panels[requestedPanel];A lookup table matches each allowed key to a function you wrote. The page only runs those functions, and no code is built from text.
Quiz
7 QUESTIONSQuestion 1 of 7Which call is direct eval?
Read the code, then predictconst run = eval; eval("1 + 1"); run("1 + 1"); (0, eval)("1 + 1");Choose an answer to see the explanation.
Question 2 of 7What does direct eval read here?
Read the code, then predictfunction demo() { const x = "local"; return eval("x"); } console.log(demo());Choose an answer to see the explanation.
Question 3 of 7What does indirect eval read here?
Read the code, then predictglobalThis.x = "global"; function demo() { const x = "local"; return (0, eval)("x"); } console.log(demo());Choose an answer to see the explanation.
Question 4 of 7What does eval do with a non-string?
Read the code, then predictconst value = 42; console.log(eval(value));Choose an answer to see the explanation.
Question 5 of 7What can a function made with
new Functionsee?Read the code, then predictglobalThis.shared = 3; function make() { const shared = 9; return new Function("return shared"); } console.log(make()());Choose an answer to see the explanation.
Question 6 of 7What does a CSP without
unsafe-evalblock in browsers?Choose an answer to see the explanation.
Question 7 of 7Which is the best way to call the formatter that
keynames?Read the code, then predictconst formatters = { upper: (text) => text.toUpperCase() }; const key = "upper";Choose an answer to see the explanation.
Key takeaways
- Direct eval is a call written exactly as
eval(...). It can see the local variables of the function that called it. - Indirect eval and
new Functionsee only global names.new Functioncan't see the local variables where it was made. - In strict mode, a
varmade inside direct eval stays inside. In sloppy mode, it leaks out. - A CSP without
unsafe-evalblocks eval,new Function, and timers given strings. - Use
JSON.parse, lookup tables,URLSearchParams, input checks, and normal functions instead.
Final definition.eval and new Function turn strings into JavaScript code while the program runs. Each sees a different set of variables, and the safety risk is almost always bigger than what you gain.
Up next: Higher-order functions.