completefrontend
Open lab My progress
THE JAVASCRIPT FIELD GUIDE

Embedding V8

Learn how programs host V8 with isolates, contexts, handles, native templates, and startup snapshots for fast JavaScript runtimes.

By the end, you can
  • 01
    Tell an isolate from a contextSay that an isolate is one copy of V8 with its own memory, and a context is one JavaScript world inside it, with its own globals.
  • 02
    Read the host's C++ stepsSay why the host's C++ code uses handles, HandleScopes, and templates, and why it picks a context before it runs code.
  • 03
    Plan a small runtimeChoose which globals a host adds, and say how a startup snapshot makes startup faster.

A host puts V8 to work

V8 is a JavaScript engine: the program that reads your JavaScript and runs it. It does these jobs:

  • It reads your code and runs it.
  • It keeps your objects in memory. This memory is called the heap.
  • It gives you the parts that are built into the language, such as Array and Math. These are called built-ins.

V8 is not a browser. It has no timers, no web page, and no network tools. A bigger program must add those. That bigger program is called the host, or the embedder. Chrome is a host. Node.js is a host. Any C++ program can be a host too. Each one puts V8 inside itself and adds its own tools around it.

Definition

Embedding V8 means a host program does three things:

  • It starts V8.
  • It makes places where JavaScript code can run.
  • It chooses which extra tools, such as timers, that code can use.

So start with one question: which global names did the host add? A global is a name your code can use anywhere, like setTimeout. In a browser, setTimeout and document both exist. In Node, setTimeout exists but document does not. V8 runs the same code in both places. The host decides what that code can reach.

Ask which globals this host addedPop out in the code editor (opens in a new tab)JavaScript
console.log(typeof globalThis.setTimeout, typeof globalThis.document);

Line 1 looks up two names on globalThis, the object that holds every global. typeof gives the kind of each value as a word. In a browser, the line prints function object: setTimeout is a function and document is an object. In Node, the same line prints function undefined, because Node has no document. The code is the same. Only the host is different.

Two windows. The browser window holds V8 plus setTimeout and document, and the line prints function object. The Node window holds V8 plus setTimeout only, and the line prints function undefined.browserV8setTimeoutdocumentfunction objectNodeV8setTimeoutno documentfunction undefined
Both places run the same V8. The browser adds setTimeout and document. Node adds setTimeout but no document.

Here are the four words this lesson uses. Each one gets its own section:

  • An isolate is one whole copy of the V8 engine, with its own heap.
  • A context is one separate JavaScript world inside an isolate, with its own globals.
  • A handle is how C++ code holds on to a JavaScript value.
  • Templates and snapshots help the host set up new worlds quickly.

Isolates: one engine with its own memory

An isolate is one running copy of the V8 engine. Each isolate has its own heap, the memory where its objects live. An object made in one isolate belongs to that isolate only. Code in another isolate cannot use it as a normal JavaScript value.

A host can make more than one isolate. It does this when it wants pieces of work kept apart, such as separate workers or separate programs.

An isolate keeps memory apart. It does not make every program safe on its own. The host still decides these things:

  • when each isolate gets to run,
  • which C++ tools each isolate can call,
  • how messages travel between isolates.

Node workers are a real example. A worker runs JavaScript in another V8 isolate, next to your main code. It has its own globalThis.

A Node worker has its own global objectJavaScript
import { Worker } from "node:worker_threads"; const worker = new Worker(  "const { parentPort } = require('node:worker_threads'); parentPort.postMessage(globalThis === global);",  { eval: true, execArgv: [] },);worker.once("message", (isOwnGlobal) => console.log(isOwnGlobal));

Line 3 starts a Node worker. Line 4 is the worker's own code. It checks whether its globalThis is the same object as its global, and sends the answer back. Line 7 prints the answer. The test sees true. This means the worker has its own global object. It does not share the main program's global object.

Real-life analogyA game console and a game world

Think of a game console. It has its own memory card. That is like an isolate with its own heap. One game running on the console is like a context. The company that built the console chose which buttons it has. The host chooses which tools JavaScript gets in the same way.

In real life: One console with its memory card
In JavaScript: One isolate with its heap
In real life: One game running on that console
In JavaScript: One context with a global object
In real life: Buttons wired by the device maker
In JavaScript: Tools the host adds, like timers
In real life: Another console
In JavaScript: Another isolate

Two other lessons use these words too. Agents and realms shows how the JavaScript rules describe these worlds. Realms and globals shows the global objects you can see from your code. This lesson looks at what the host program does with V8.

Contexts: separate JavaScript worlds

A context is one separate JavaScript world inside an isolate. Each context has:

  • its own global object,
  • its own copies of the built-ins, such as Array.

Before the host runs any code, it picks which context the code runs in. That way, two unrelated pieces of code don't share one global object by accident.

Node lets you try this from JavaScript. Its node:vm module can make new contexts. It is not the full C++ tool kit that hosts use, but it shows the same idea. The code below makes one context with a global called price. Then it runs a tiny piece of code inside that context.

Run one small expression in a Node contextJavaScript
const vm = require("node:vm");const context = vm.createContext({ price: 10 });console.log(vm.runInContext("price * 2", context));

Line 1 loads Node's vm module. Line 2 makes a new context. Its global object starts with price: 10. Line 3 runs price * 2 inside that context and prints 20. The price here is not a variable from the file around it. It is the global price of the new context.

A context holds more than names. Its built-ins are separate too. So an array made in a new Node context gives false for instanceof Array when you check it from outside. instanceof asks: "was this made by this exact constructor?" The array still works like any array. It was just made by the other context's Array.

The step-through below uses a small model of isolates and contexts written for this lesson. It makes one isolate with two contexts and runs the same code in each one.

Step through two separate contexts
Step 0 of 7Ready
Your turn: follow the blue line

Step through two contexts in one model isolate. Each context keeps its globals in its own plain object.

Running in
  1. script
Next: line 1
const first = createContext(isolate, { price: 10 });const second = createContext(isolate, { price: 3 });console.log(run(first, "price * 2"));console.log(run(second, "price * 2"));
CallStoreChangeResultBlue line: runs next. Ran: already executed.
Recent returnsNothing yet. Start with the blue line.
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.
One isolate holds two contexts, first and second. The first has price 10, so price times 2 gives 20. The second has price 3, so price times 2 gives 6.one isolateprice * 2price: 10first20price * 2price: 3second6
One isolate holds two contexts. Each context has its own price, so the same price * 2 gives 20 in the first and 6 in the second.

Proof from Node contexts

The step-through above is a model. It keeps one plain object for each context's globals. Now let's check the same idea in real Node, with node:vm. A name you put in one context does not appear in another context.

Node contexts keep their globals and Array apartJavaScript
import vm from "node:vm"; const first = vm.createContext({ price: 10 });const second = vm.createContext({});console.log(vm.runInContext("price * 2", first));console.log(vm.runInContext("typeof price", second));console.log(vm.runInNewContext("[]") instanceof Array);

Line by line:

  1. Line 5 runs price * 2 in the first context. It prints 20.
  2. Line 6 asks for the type of price in the second context. That context is empty, so it prints undefined.
  3. Line 7 makes an array in a brand new context. It checks instanceof Array from outside and prints false, because the two contexts have different Array constructors.
What this proof shows

The lesson test runs this code in real Node. It does not run in your browser, because browsers can't load node:vm. It shows that contexts keep their globals and constructors apart. It does not show that a context is a separate operating-system process. A context alone is also not a safe place to run code you don't trust.

Browsers often use one context for each window and each frame on a page. A host can enter one context, run code, and then leave and go back to the context it used before. Because the host always picks the context on purpose, code never runs in the wrong global world by mistake.

Handles and HandleScopes

JavaScript code uses objects directly. C++ code that hosts V8 can't do that. It must use a handle: a small holder that points to a V8 value.

Why not just keep the memory address of the object? Because V8 has a garbage collector, a helper that cleans up unused objects. While it tidies the heap, it may move an object to a new place in memory. V8 updates every handle when that happens. A saved raw address would still point to the old place, which is wrong.

There are two kinds of handle to know:

  • A local handle is for short work. It lives inside a HandleScope, a group that holds local handles. When the scope ends, all of its local handles go away together.
  • A persistent handle, written v8::Persistent or v8::Global, is for longer work. It does not go away when the scope ends. The host must reset it by hand when it no longer needs the value.

One more rule: a function can't simply return a local handle from a scope that is ending. It must use an EscapableHandleScope, which lets one handle escape to the outer scope.

A short C++ sketch of a HandleScopeC++
// Short C++ sketch of a host's code. It is not JavaScript.v8::HandleScope scope(isolate);v8::Local<v8::String> name = v8::String::NewFromUtf8Literal(isolate, "Asha");// When the scope ends, its local handles go away together.

This is C++, not JavaScript, and it is shortened. Line by line:

  1. Line 2 opens a scope for local handles.
  2. Line 3 makes a V8 string, "Asha", and a local handle called name that points to it.
  3. Line 4 is a comment with the main rule: when the scope ends, its local handles go away together.

After that, the string can be cleaned up, but only if nothing else still points to it.

Left: inside the scope, a handle called name points to the V8 string Asha. Right: after the scope ends, the handle is gone and the string Asha has nothing pointing to it.inside the scopenamescopename is a handle"Asha"after the scope ends"Asha"no handle left
Inside the scope, the handle name points to the string "Asha". When the scope ends, the handle goes away. If nothing else points to the string, it can be cleaned up.
Real-life analogyA tray at a buffet

Picture a tray at a buffet. While you hold the tray, the plates on it stay with you. When you hand back the tray, all its plates go back at the same time. You don't return them one by one.

In real life: A tray carries your plates
In JavaScript: A HandleScope holds local handles
In real life: A plate stays with your tray
In JavaScript: A handle keeps a V8 value from being cleaned up
In real life: Hand back the tray once
In JavaScript: End the scope, and all its local handles go away
In real life: Keep one plate for later
In JavaScript: Use a persistent handle, and reset it yourself

So handles do for C++ what variables do for JavaScript. A JavaScript variable keeps its object from being cleaned up. A handle keeps a V8 value from being cleaned up while C++ code is using it.

Templates add C++ functions to JavaScript

How does a host give JavaScript a new function, like setTimeout? It writes the function in C++. Then it uses a FunctionTemplate. A FunctionTemplate is a recipe V8 uses to make a JavaScript function that runs C++ code when called. The C++ code it runs is called a callback.

The host puts that function on a context's global object. When JavaScript calls it, V8 hands the call to the C++ callback, along with the arguments.

First, here is the same idea in plain JavaScript. A new, empty object stands in for a fresh global object. We add one function to it, then call it. This is not C++. It just shows the host's choice before the C++ sketch.

Add one function to a new global objectPop out in the code editor (opens in a new tab)JavaScript
const globalObject = {}; globalObject.double = (price) => price * 2;console.log(globalObject.double(10));

Line 1 makes an empty object called globalObject. Line 3 adds a function named double to it. Line 4 calls double(10) and prints 20. A real host does this with V8's C++ tools and a C++ callback. The rule is the same: if the host doesn't add a function, JavaScript can't call it.

A function card, price times 2, is put on the global object under the name double. Then double(10) gives 20.(price) => price * 2a functioninstalldoubleglobalglobalObject20double(10)
Line 3 puts a function on the global object under the name double. Line 4 calls double(10) and gets 20.
A short C++ sketch of a FunctionTemplateC++
// Short C++ sketch of a host's code. It is not JavaScript.auto global = v8::ObjectTemplate::New(isolate);global->Set(v8::String::NewFromUtf8Literal(isolate, "double"),            v8::FunctionTemplate::New(isolate, DoubleCallback));auto context = v8::Context::New(isolate, nullptr, global);

Line by line:

  1. Line 2 makes a template for the global object: a recipe for what the global object will hold.
  2. Lines 3 and 4 add the name double to that recipe. The name points to a FunctionTemplate that runs the C++ callback DoubleCallback.
  3. Line 5 makes a new context whose global object follows that recipe. Code in this context can call double().

The browser's document and Node's built-in tools are added in this same way, just with many more functions.

Build a tiny host

Now you play the host. The playground below uses this lesson's small model. It does these things each time you click:

  • It makes a new isolate.
  • It makes a context with one global, price: 10.
  • It adds only the functions you turned on.
  • It runs the code you picked.

The model understands only a very small language: numbers, names, + - * /, round brackets, and calls to functions the host added. It never runs your text as real JavaScript. It does not use eval or new Function, the two ways to turn text into running code.

Try this: pick double(price). Then turn Add double off and look at the result again. The result changes to a message saying the function is missing. The Reset button puts back the first setup.

Playground: choose which functions the host adds
The tiny host's codeJavaScript
const isolate = createIsolate();const context = createContext(isolate, { price: 10 }, { double });const result = run(context, "double(price)");console.log(result);
What the host madeisolate 1
context1

Its globals: price = 10

functions addeddouble

Code can call only these functions.

result20

The tiny language ran the code you picked.

Try it yourself

The context has one global, price = 10. The code double(price) gives 20.

This tiny language runs only the few pieces of code on the buttons. It never runs real JavaScript, and it is not V8.

This is a model for learning. It is not how V8 reads code, and it has no handles or safety checks. Its tiny language keeps the page safe while it shows the host's choice. A real host gives V8 full JavaScript code and handles errors with V8's C++ tools.

Snapshots make startup faster

Every new context needs its built-ins and a global object, ready to use. Making all of that from nothing takes time. A startup snapshot is a saved copy of a heap that is already set up. V8 can load that saved copy instead of building everything again, so startup is faster.

V8's guide to custom startup snapshots works in two steps:

  1. Set up a heap once, and save it as a snapshot.
  2. When a new isolate starts, tell it to load that snapshot.

A snapshot is not a backup of a running app. It only saves what is inside V8's heap. It can't save things outside the heap, such as open files. And any value saved in it stays fixed. If you save the current time in a snapshot, every start gets that same old time.

A short C++ sketch of loading a startup snapshotC++
// Short C++ sketch of a host's code. It is not JavaScript.v8::Isolate::CreateParams params;params.snapshot_blob = custom_snapshot_blob;auto* isolate = v8::Isolate::New(params);

Line by line:

  1. Line 2 makes a settings object, params, for a new isolate.
  2. Line 3 puts the saved snapshot into those settings, as snapshot_blob.
  3. Line 4 starts a new isolate with those settings.

Real code needs more setup steps than this. The sketch only shows where the snapshot goes in.

Node and Chrome both use startup snapshots. To see why this matters to people using an app, read startup performance. Work done before the program starts is work the user doesn't wait for. The snapshot must also give the same result every time, and it must match the exact V8 build that loads it.

What runtime authors decide

A runtime is a host program that runs JavaScript, such as Node or a browser. Most developers never call V8's C++ tools. But the people who build runtimes make choices that every developer feels. They choose:

  • which globals exist,
  • how their C++ functions report errors,
  • how many isolates to make,
  • when to make new contexts,
  • what can safely go in a startup snapshot.

That is why the same V8 can feel different in a browser, in Node, or in a test tool. The core JavaScript language comes with V8. Extra tools, such as timers and document, come with the host. So before you use a tool, check which runtime will run your code.

Isolate, context, or host?
  • Has its own heap: the memory for one copy of V8.
  • Has its own global object and its own built-ins, like Array.
  • Decides whether setTimeout exists.
  • Keeps a V8 value from being cleaned up while C++ code uses it.
  • Can hold price: 10 without changing any other JavaScript world.
  • Picks a saved, ready-made heap to load at startup.
Try it yourself
0 of 6 correct

Put each card under the part that does that job. Read the explanation after each choice.

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

This helps when you write a library too. Don't assume a browser document exists in code that might run in Node. Keep code that needs host tools in one small place. Write tests that say which runtime tools you need.

A host that uses V8 directly has more jobs. Here are three:

  • It decides when queued promise callbacks run. These callbacks are called microtasks. The host can let V8 run them automatically, or run them itself at points it picks.
  • It must follow V8's C++ tools as they change between V8 versions. A Node add-on can use Node-API instead. Node-API stays the same across Node versions.
  • It must not trust separate isolates alone for safety. Packages such as isolated-vm run code in separate V8 isolates inside Node. That alone does not make code you don't trust safe to run.

Common mix-ups

  • “V8 is the browser.” V8 is only the engine. The browser adds the rest around it: the web page, fetch, drawing on screen, and the loop that handles events.
  • “Every context is a separate isolate.” One isolate can hold many contexts. Each context has its own globals.
  • “A context makes untrusted code safe by itself.” A new global object is not enough. To run code you don't trust, the host needs a full set of safety rules.
  • “A handle is a JavaScript variable.” A handle is used by C++ code, through V8's C++ tools. JavaScript code never sees it.
  • “A snapshot saves everything an app is doing.” A snapshot saves only a heap that was set up ahead of time. It can't save things outside the heap, such as open files.

The table below lists terms that are easy to mix up. Read each row as: "this term means this, and it does not mean that."

Embedding words that are easy to mix up
WordWhat it meansWhat it does not mean
V8The engine that reads and runs JavaScript code.It is not the whole browser, and it is not the whole Node.js program.
Host APIA tool the host adds around V8, such as setTimeout or document.It is not part of the JavaScript language itself.
Separate contextsTwo JavaScript worlds in one isolate. Each has its own globals and built-ins.They are not enough, on their own, to make code you don't trust safe to run.
SnapshotA heap saved after setup, so the next start is faster.It is not a tool for looking at memory, and it can't save just any data.

A good habit: before you explain a behavior, name the part that causes it. Is it the JavaScript language, the V8 engine, V8's C++ tools, or the host? That one question clears up most confusion about globals, separate worlds, and startup.

Practice exercises

Exercise 1 · Warm-upGuess what a browser prints

What does this line print in a browser? Then say who decides that document exists: V8 or the host.

Starter codePop out in the code editor (opens in a new tab)JavaScript
console.log(typeof globalThis.setTimeout, typeof globalThis.document);

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

    Exercise 2 · Warm-upGuess what two separate objects print

    Each object stands in for the global object of one context. Type the two printed lines in order, with a comma between them.

    Starter codePop out in the code editor (opens in a new tab)JavaScript
    const first = { price: 10 };
    const second = {};
    console.log(first.price * 2);
    console.log(typeof second.price);

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

      Exercise 3 · PracticeName the function the host adds

      A tiny host works with prices. Which function does it add to its global object?

      Starter codePop out in the code editor (opens in a new tab)JavaScript
      const globalObject = {};
      
      globalObject.double = (price) => price * 2;
      console.log(globalObject.double(10));

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

        Exercise 4 · PracticeWhat goes away when a scope ends

        When a HandleScope ends, which handles go away together?

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

          Exercise 5 · ChallengePlan a new runtime

          You are building a small runtime that runs price rules for tests. What must you decide before you run a rule?

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

            Exercise 6 · ChallengeSay what a snapshot saves you

            A runtime starts many times a day. What work, done again at every start, can a startup snapshot skip?

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

              Check your understanding

              For each question, first ask which part it is about: V8 itself, a context, the C++ code in the host, or a choice the host makes.

              Embedding V8 quiz · 7 questionsScore: first tries count
              1. Question 1 of 7Who decides whether document exists on globalThis?

                Choose an answer to see the explanation.

              2. Question 2 of 7What does this code print for the two objects?

                Read the code, then predictPop out in the code editor (opens in a new tab)JavaScript
                const first = { price: 10 };
                const second = {};
                console.log(first.price * 2, typeof second.price);

                Choose an answer to see the explanation.

              3. Question 3 of 7Which sentence describes an isolate?

                Choose an answer to see the explanation.

              4. Question 4 of 7Why does C++ code hold a V8 object through a local handle?

                Choose an answer to see the explanation.

              5. Question 5 of 7What does a FunctionTemplate describe?

                Choose an answer to see the explanation.

              6. Question 6 of 7What is a startup snapshot for?

                Choose an answer to see the explanation.

              7. Question 7 of 7Which Node tool lets JavaScript make a context with its own globals?

                Choose an answer to see the explanation.

              Key takeaways

              • V8 runs JavaScript. The host decides which extra tools and globals your code gets.
              • An isolate is one copy of V8 with its own heap. A context is one JavaScript world inside it, with its own globals.
              • Handles let C++ code hold V8 values safely. A HandleScope lets go of all its local handles at once.
              • Templates are recipes. They let a host add C++ functions and objects to JavaScript.
              • A startup snapshot is a heap saved after setup, so each start does less work.

              Remember the one-liner.
              A host turns V8 into a runtime by choosing its worlds, its tools, and how it starts.

              Coming next: how Node.js is built. You'll see how V8, a library called libuv, and C++ code fit together into one server runtime.

              CompleteFrontend Clear concepts. Working examples.