completefrontend
Open lab My progress
THE JAVASCRIPT FIELD GUIDE

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.

By the end you can
  • 01
    Tell the three apartSay which variables direct eval, indirect eval, and new Function can see.
  • 02
    Spot the dangerExplain why running text as code is a security risk, and how CSP blocks it.
  • 03
    Choose 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.

The short rule

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.

Real-life analogyeval is a note from a stranger

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 THROUGH

There are two kinds of eval call:

  • Direct eval is a call written exactly as eval(...), where the name eval still 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, then indirect(...)
  • the comma trick (0, eval)(...)
  • the optional call eval?.(...)
  • a property read like globalThis.eval(...)
Three ways to call evalPop out in the code editor (opens in a new tab)JavaScript
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.

Left: eval("name") looks inside readName, which holds "local name". Right: indirect("name") looks only at the global names, which hold "global name".eval("name")"local name"readNamefinds the local nameindirect("name")"global name"globalfinds the global name
Direct eval("name") looks inside readName and finds the local name. The indirect call sees only global names, so it finds "global name".
A local function named eval is not evalPop out in the code editor (opens in a new tab)JavaScript
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.

Step through: where does eval find x?
Step 0 of 4Ready
Your turn: follow the blue line

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.

Running in
  1. script
Next: line 5
function readX() {  const x = "local x";  return eval("x");}
CallStoreChangeResultBlue line: runs next. Ran: already executed.
Recent returnsNothing yet. Start with the blue line.
Choose the call shape
A guided replay recorded from real JavaScript calls, not an engine debugger. Step follows executed statements; Back reviews a snapshot. Reset starts a fresh run.

(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.

Browser lab: four fixed eval snippets
The code inside the iframePop out in the code editor (opens in a new tab)JavaScript
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 }, "*");  }});
Sandboxed iframefixed snippets

Preparing the sandboxed frame…

casenot run yet
statuswaiting
valuechoose a snippet
Try it yourself

This 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.

The iframe is sandboxed with allow-scripts and without allow-same-origin. Its scripts run, but it counts as a separate site that can't touch this page.

A var made inside eval

BROWSER LAB

Direct eval can also make new variables. What happens to a var made inside it depends on the mode:

  • In sloppy mode, the var is added to the function that called eval. It leaks out.
  • In strict mode, direct eval keeps its own set of variables. The var is gone when eval ends.
The two var cases from the labPop out in the code editor (opens in a new tab)JavaScript
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.

Strict eval keeps its var insidePop out in the code editor (opens in a new tab)JavaScript
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 runs strings

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

INTERACTIVE

new 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.

Real-life analogynew Function is a vending machine in the lobby

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 a and b
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
new Function sees only global namesPop out in the code editor (opens in a new tab)JavaScript
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 add with two parameters, so line 9 prints 5.
  • Lines 3 to 6 make a local secret and build a function that tries to read it. That function can't see secret. So line 10 throws, and line 11 prints ReferenceError, the error for a name that doesn't exist.
  • Line 8 builds a function that reads the global shared, so line 12 prints 7.
Left: readShared() reaches the global box shared, which holds 7, and gives 7. Right: readSecret() cannot reach the box secret, which holds 10 and is local to makeSecretReader, so it throws ReferenceError.readShared()7shareda global variable7readSecret()10secretlocal to makeSecretReaderReferenceError
A function from new Function sees only global names. So readShared() finds shared, but readSecret() can't see the local secret and throws.
new Function lab: parameters, local names, global names
Three new Function casesPop out in the code editor (opens in a new tab)JavaScript
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 }, "*"); }});
Sandbox resultglobal names only

Preparing the sandboxed frame…

casenot run yet
statuswaiting
valuechoose a case
Try it yourself

A 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.

The new functions exist only inside the sandboxed iframe.
The body string turns on strict modePop out in the code editor (opens in a new tab)JavaScript
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 LAB

The 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.

Real-life analogyCSP is a rule at the front desk

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
Two browser frames run eval("21 * 2"). The frame with unsafe-eval gives 42. The frame without unsafe-eval throws EvalError.with unsafe-evaleval("21 * 2")42no unsafe-evaleval("21 * 2")EvalError
Both frames run the same eval("21 * 2"). With unsafe-eval in the policy it gives 42; without it, the browser throws an EvalError.
CSP lab: with and without unsafe-eval
The same eval call, two policiesPop out in the code editor (opens in a new tab)HTML
<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>
Two sandboxed framesCSP decides

Preparing the sandboxed frame…

frame policyeval result
with unsafe-evalwaiting
without unsafe-evalwaiting
Try it yourself
Reset rebuilds both frames with the same code.

Both 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.

The error message is different in each browser. Rely on the error type, EvalError, not on the words.

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 Function
  • setTimeout or setInterval called with a string instead of a function
Pass a function, not a stringPop out in the code editor (opens in a new tab)JavaScript
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

SORTER

Most 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
Read data and pick actions without evalPop out in the code editor (opens in a new tab)JavaScript
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 a switch statement.
  • For URL or form values, read them with URLSearchParams, turn text into a number with Number(), and check that the value makes sense.
Pick from allowed actionsPop out in the code editor (opens in a new tab)JavaScript
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.

A tag holding "greet" points to a cart called actions, which holds two functions, greet and count. An arrow from the cart leads to the result card "Hello, Asha".actions[action]("Asha")"greet"the keygreetcountactions"Hello, Asha"the result
The key "greet" picks one function from the actions table. Only functions already in the table can run, so no text becomes code.
Check the input after reading itPop out in the code editor (opens in a new tab)JavaScript
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.

Read only the tiny language you needPop out in the code editor (opens in a new tab)JavaScript
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.

Is eval needed here?
  • 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=2 and turn it into a number
Try it yourself
0 of 6 correct

Sort each situation by the safest way to handle it.

Choose a category for every card. You can change an answer at any time; Reset clears them all.

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.

The speed advice

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.

Ways to run text as code, compared
What you writeWhich 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 evalIt 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 parserIt 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 EXERCISES
Exercise 1 · Warm-upPredict direct eval

Type exactly what the console prints.

Starter codePop out in the code editor (opens in a new tab)JavaScript
function demo() {
  const x = "nearby";
  return eval("x");
}
console.log(demo());

Answer, then press Check. Spacing and letter case don’t matter.

    Exercise 2 · Warm-upPredict indirect eval

    Type exactly what the console prints.

    Starter codePop out in the code editor (opens in a new tab)JavaScript
    globalThis.x = "global";
    function demo() {
      const x = "nearby";
      return (0, eval)("x");
    }
    console.log(demo());

    Answer, then press Check. Spacing and letter case don’t matter.

      Exercise 3 · Practiceeval with a non-string

      Guess the true or false it prints.

      Starter codePop out in the code editor (opens in a new tab)JavaScript
      const value = { ok: true };
      console.log(eval(value) === value);

      Answer, then press Check. Spacing and letter case don’t matter.

        Exercise 4 · PracticeWhat new Function can see

        Guess the number it prints.

        Starter codePop out in the code editor (opens in a new tab)JavaScript
        globalThis.tax = 5;
        function make() {
          const tax = 99;
          return new Function("price", "return price + tax");
        }
        console.log(make()(10));

        Answer, then press Check. Spacing and letter case don’t matter.

          Exercise 5 · ChallengeRead JSON without eval

          The data arrives as JSON text. Which method should you use to read it?

          Starter codePop out in the code editor (opens in a new tab)JavaScript
          const user = JSON.parse('{"name":"Ada","admin":false}');
          console.log(user.name + ":" + user.admin);

          Answer, then press Check. Spacing and letter case don’t matter.

            Exercise 6 · ChallengePick a page panel safely

            A dashboard gets a panel name from a menu. Name the safe pattern this starter code uses instead of building a string for eval.

            Starter codePop out in the code editor (opens in a new tab)JavaScript
            const panels = {
              profile: (user) => "Profile: " + user,
              orders: (user) => "Orders for " + user,
            };
            const requestedPanel = "profile";
            const render = panels[requestedPanel];
            console.log(render("Asha"));

            Answer, then press Check. Spacing and letter case don’t matter.

              Quiz

              7 QUESTIONS
              Check your understanding · 7 questionsScore: first tries count
              1. Question 1 of 7Which call is direct eval?

                Read the code, then predictPop out in the code editor (opens in a new tab)JavaScript
                const run = eval;
                eval("1 + 1");
                run("1 + 1");
                (0, eval)("1 + 1");

                Choose an answer to see the explanation.

              2. Question 2 of 7What does direct eval read here?

                Read the code, then predictPop out in the code editor (opens in a new tab)JavaScript
                function demo() {
                  const x = "local";
                  return eval("x");
                }
                console.log(demo());

                Choose an answer to see the explanation.

              3. Question 3 of 7What does indirect eval read here?

                Read the code, then predictPop out in the code editor (opens in a new tab)JavaScript
                globalThis.x = "global";
                function demo() {
                  const x = "local";
                  return (0, eval)("x");
                }
                console.log(demo());

                Choose an answer to see the explanation.

              4. Question 4 of 7What does eval do with a non-string?

                Read the code, then predictPop out in the code editor (opens in a new tab)JavaScript
                const value = 42;
                console.log(eval(value));

                Choose an answer to see the explanation.

              5. Question 5 of 7What can a function made with new Function see?

                Read the code, then predictPop out in the code editor (opens in a new tab)JavaScript
                globalThis.shared = 3;
                function make() {
                  const shared = 9;
                  return new Function("return shared");
                }
                console.log(make()());

                Choose an answer to see the explanation.

              6. Question 6 of 7What does a CSP without unsafe-eval block in browsers?

                Choose an answer to see the explanation.

              7. Question 7 of 7Which is the best way to call the formatter that key names?

                Read the code, then predictPop out in the code editor (opens in a new tab)JavaScript
                const 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 Function see only global names. new Function can't see the local variables where it was made.
              • In strict mode, a var made inside direct eval stays inside. In sloppy mode, it leaks out.
              • A CSP without unsafe-eval blocks 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.

              CompleteFrontend Clear concepts. Working examples.