logo
Developer Reference

Virtual Runtime Environment

A drop-in browser-based preview shell with a full devtools API. No servers. No build step. Drop in two scripts and get a live preview with console, network, DOM inspector, and a programmatic API for agents, IDEs, and sandbox tools.


Section 1

Quick start

One file is required. Drop it into your project alongside your virtual filesystem definition.

File Purpose
https://preview.codevre.dev/v1/vre.min.js Core shell — classic script tag
https://preview.codevre.dev/v1/vre.esm.js Core shell — ES module import

Minimal HTML — classic script:

<!-- 1. Load styles and scripts -->
<script src="https://preview.codevre.dev/v1/vre.min.js"></script>

<div id="vre"></div>
<script src="file-system.js"></script>

<script>
// Basic mode
const vre = VRE.mount({
  ownerId: 'YOUR_OWNER_ID',
  container: "#vre",
  fileSystem,
  entryPath: "/index.html",
  devtoolsOpen: true,
});
vre.run();

// OR strict backend-protected mode
const secureVre = VRE.mount({
  accessCheckUrl: 'https://your-backend.com/validate',
  container: "#vre",
  fileSystem,
  entryPath: "/index.html",
  devtoolsOpen: true,
});
secureVre.run();
</script>

Minimal HTML — ES module:

<div id="vre"></div>
<script src="file-system.js"></script>

<script type="module">
import { VRE } from "https://preview.codevre.dev/v1/vre.esm.js";

const vre = VRE.mount({
  ownerId: 'YOUR_OWNER_ID',
  container: "#vre",
  fileSystem: window.fileSystem,
  entryPath: "/index.html",
  devtoolsOpen: true,
});
vre.run();
</script>

For stricter deployments, use accessCheckUrl instead of ownerId. This allows your backend to securely exchange a private API key for a short-lived runtime token without exposing the API key in the browser.

Your accessCheckUrl endpoint should validate the current user, session, or application before exchanging a runtime token. Do not expose a public endpoint that blindly returns runtime tokens to any caller.

The basic ownerId flow is still protected through authorized domain restrictions configured in the dashboard.

Section 2

File structure

your-project/
├── index.html              ← your demo page
├── https://preview.codevre.dev/v1/vre.min.js               ← core shell (required)
└── file-system.js          ← your virtual filesystem

Wrap your filesystem in an IIFE and attach to window to avoid variable collision with the shell:

(function () {
  const fileSystem = {
    // ... your filesystem object
  };
  window.fileSystem = fileSystem;
})();
Section 3

Virtual filesystem

A plain JSON tree of directory and file nodes. Pass it to mount() as fileSystem.

VRE’s virtual filesystem is intentionally lightweight and runtime-focused. It does not attempt to fully replicate every Node.js filesystem, process, or module abstraction directly. For advanced compatibility layers, you can pair the VFS with external shims such as BrowserFS, persistence adapters, or custom runtime bridges while keeping them synchronized with the VRE filesystem state.

const fileSystem = {
  id: "my-project",
  name: "my-project",
  path: "/",
  type: "directory",
  children: [
    {
      id: "idx_html",
      name: "index.html",
      path: "/index.html",
      type: "file",
      extension: "html",
      mimetype: "text/html",
      content: `<!DOCTYPE html>...`,
    },
    {
      id: "style_css",
      name: "style.css",
      path: "/style.css",
      type: "file",
      extension: "css",
      mimetype: "text/css",
      content: `body { margin: 0; }`,
    },
    {
      id: "app_js",
      name: "app.js",
      path: "/app.js",
      type: "file",
      extension: "js",
      mimetype: "application/javascript",
      content: `console.log("hello");`,
    }
  ]
};

Nested directories:

{
  id: "src_dir",
  name: "src",
  path: "/src",
  type: "directory",
  children: [
    {
      id: "main_js",
      name: "main.js",
      path: "/src/main.js",
      type: "file",
      mimetype: "application/javascript",
      content: `export const hello = "world";`
    }
  ]
}

File source types:

source Description Required fields
"vfs" Content lives in the filesystem object (default) content
"host" Fetched from your server at run time hostPath, content: null
"url" Loaded natively by the browser from a CDN url, content: null
// VFS file (default — content is inline)
{ type: "file", source: "vfs", path: "/app.js", content: `...` }

// Host file (fetched from your server before run)
{ type: "file", source: "host", path: "/logo.png", hostPath: "/assets/logo.png", content: null }

// URL file (browser loads it natively — appears in Sources panel)
{ type: "file", source: "url", path: "/vendor/chart.js",
  url: "https://cdnjs.cloudflare.com/ajax/libs/Chart.js/4.4.1/chart.umd.min.js", content: null }
Section 4

Mount options

Option Type Default Description
ownerId string null Basic access mode. Your Codevre owner ID. Either ownerId or accessCheckUrl is required.
accessCheckUrl string null Strict access mode. Backend endpoint that validates the current app, user, or session before exchanging a private API key for a short-lived runtime token.
container string | Element Where to mount the shell (required)
fileSystem object null Virtual filesystem tree
entryPath string "/index.html" File to load on run
src string null Load a real URL instead of VFS. Bypasses runtime protection — don't use for secured deployments.
devtoolsOpen boolean false Open devtools panel on start
devtoolsHeight number 240 Devtools panel height in px
theme string | object 'dark' Shell colour theme: 'dark', 'light', or a token object
blockPatterns string[] [] URL substrings to hide from console, network, and sources panels
cssPath string vre.css URL to a custom stylesheet. Overrides the default shell styles entirely.

Custom stylesheet:

The shell loads styles.min.css by default. You can download an unminified version of that file as a starting point, modify it, host it, and pass your version via cssPath.

const vre = VRE.mount({
  ownerId: 'YOUR_OWNER_ID',
  container: "#vre",
  fileSystem,
  entryPath: "/index.html",
  cssPath: "https://your-cdn.com/your-vre-theme.css", // ← your custom stylesheet
});
The unminified default stylesheet is available at preview.codevre.dev/css/vre.css — use it as a base for your own theme.
Section 5

Filesystem API

All filesystem methods are synchronous and return api for chaining unless noted.

readFile( path ) sync

Read the content of a file from the VFS. Returns null if the file does not exist.

Returnsstring | ArrayBuffer | null
const html = vre.readFile('/index.html');
const css  = vre.readFile('/style.css');
console.log(html.length); // character count
updateFile( path, content ) sync

Update an existing file's content and hot-reload the preview. Throws if the file does not exist — use addFile to create new files.

vre.updateFile('/style.css', 'body { background: red; }');
addFile( path, content, opts? ) sync

Add a new file to the VFS. Throws if the path already exists unless overwrite is true.

vre.addFile('/utils.js', 'export const add = (a, b) => a + b;');
vre.addFile('/style.css', 'body {}', { overwrite: true });
vre.addFile('/data.json', '{"key":"value"}', { mimetype: 'application/json' });
deleteFile( path ) sync

Remove a file from the VFS. Throws { code: 'NOT_FOUND' } if the path does not exist.

vre.deleteFile('/old-script.js');
renameFile( fromPath, toPath ) sync

Move or rename a file within the VFS.

vre.renameFile('/app.js', '/src/app.js');
listFiles( opts? ) sync

List all files in the VFS as a flat metadata array. Does not include directory entries.

Returns{ path, mimetype, size }[]
const files    = vre.listFiles();
const jsFiles  = vre.listFiles({ extensions: ['js'] });
const srcFiles = vre.listFiles({ prefix: '/src' });
diffFile( path, newContent ) sync

Line-by-line diff between the current file content and a new string.

Returns{ lineNo, type: 'add'|'remove'|'context', text }[]
const diff = vre.diffFile('/app.js', newCode);
diff.forEach(line => {
  if (line.type === 'add')    console.log('+', line.text);
  if (line.type === 'remove') console.log('-', line.text);
});
snapshotFilesystem() · restoreSnapshot( token ) sync

Save and restore the entire filesystem state. snapshotFilesystem() returns an opaque token string.

const token = vre.snapshotFilesystem();
vre.updateFile('/style.css', 'body { background: red; }');
vre.restoreSnapshot(token); // rolls back
setFileSystem( fs, opts? ) · getFileSystem() sync

Replace or read back the entire filesystem object.

vre.setFileSystem(newFs, { entryPath: '/index.html' });
const fs = vre.getFileSystem();
Section 6

Lifecycle API

run( fs?, opts? ) · stop() · reload() · destroy() sync

run() starts or restarts the preview. stop() returns to idle. reload() is an alias for run(). destroy() stops and removes the shell from the DOM entirely.

vre.run();
vre.run(newFs, { entryPath: '/index.html' });
vre.stop();
vre.destroy();
getStatus() · isRunning() · isReady() sync

getStatus() returns a plain serialisable object safe to pass directly to an LLM. isRunning() and isReady() return booleans.

const status = vre.getStatus();
// { running, ready, entryPath, inspecting, activeTab, devtoolsOpen }
onReady() · waitForLoad( opts? ) · reset( opts? ) async

onReady() resolves when the engine becomes ready (immediately if already ready). waitForLoad() resolves when the preview finishes loading. reset() hard-resets and clears all devtools state.

await vre.onReady();
vre.run();
const { entryPath } = await vre.waitForLoad({ timeout: 15000 });
vre.reset({ fileSystem: newFs, entryPath: '/index.html' });
Section 7

Navigation API

open( path, opts? ) · back() · forward() sync

Navigate to a path. open() starts the preview if not already running. Pass { push: false } to skip history.

vre.open('/about/index.html');
vre.open('/contact.html', { push: false });
vre.back(); vre.forward();
setEntryPath( path, opts? ) · getEntryPath() sync

Set the entry path without navigating. getEntryPath() returns the current entry path string.

vre.setEntryPath('/about/index.html');
console.log(vre.getEntryPath()); // '/index.html'
getHistory() · clearHistory() · setViewport( preset ) sync

setViewport() accepts a named preset ('mobile' 390px, 'tablet' 768px, 'desktop' full) or an explicit { width } object.

vre.setViewport('mobile');
vre.setViewport({ width: 480 });
const history = vre.getHistory();
// ['/index.html', '/about/index.html']
setExternalMode( active ) sync

Open or close the preview in a separate browser window.

vre.setExternalMode(true);  // opens external window
vre.setExternalMode(false); // returns to embedded preview
Section 8

Devtools data API

Programmatic access to console, network, and sources panels — without reading the UI.

getConsoleLogs( opts? ) · getErrors() sync

All captured console entries since last clear. Filter by level, filter (text), or limit. getErrors() is a shorthand for error-level only.

Returns{ level, message, origin: { file, line }|null, timestamp }[]
const errors = vre.getConsoleLogs({ level: 'error' });
const recent = vre.getConsoleLogs({ limit: 20 });
const errs   = vre.getErrors();
getNetworkLog( opts? ) sync

All captured network requests since last clear. Filter by method, status, filter, or limit.

Returns{ url, method, status, duration, destination, timestamp }[]
const failed = vre.getNetworkLog({ status: 404 });
const api    = vre.getNetworkLog({ filter: '/api/' });
getSources() sync

All source files captured by the engine, as a flat array.

Returns{ domain, path, fullUrl }[]
const sources = vre.getSources();
// [{ domain: 'my-project', path: '/app.js', fullUrl: '/app.js' }, ...]
setLogBuffer( n ) sync

Set the maximum number of console/network entries kept in memory. Default is 500.

vre.setLogBuffer(1000);
switchTab( tab ) · getActiveTab() · setDevtoolsOpen( open ) · getDevtoolsOpen() · clearDevtools() sync

Tab options: 'console' · 'network' · 'sources' · 'elements'. getActiveTab() returns the currently active tab id. getDevtoolsOpen() returns true if the panel is visible. clearDevtools() clears all panels at once.

vre.switchTab('network');
console.log(vre.getActiveTab()); // 'network'
vre.setDevtoolsOpen(true);
if (!vre.getDevtoolsOpen()) vre.setDevtoolsOpen(true);
vre.clearDevtools();
refreshSources() · refreshNetwork() sync

Manually refresh the Sources or Network panel.

vre.refreshSources();
vre.refreshNetwork();
Section 9

DOM inspector API

getDOM( opts? ) · queryDOM( selector ) async / sync

getDOM() returns the last full DOM snapshot (async). queryDOM() runs a CSS-selector query against it (sync). Supports: tag, .class, #id, [attr], [attr=value].

await vre.getDOM({ fresh: true });
const buttons = vre.queryDOM('button');
const cards   = vre.queryDOM('.card');
const inputs  = vre.queryDOM('[type=text]');
console.log(buttons[0].tag);        // 'button'
console.log(buttons[0].attributes); // [{ name, value }, ...]
console.log(buttons[0].path);       // [0, 1, 3, ...] — path for getElement()
getElement( path ) async

Get full inspect detail for a DOM node by its path array (from queryDOM()). Returns tag, id, classList, computedStyle, boundingRect, and more.

const el = await vre.getElement(buttons[0].path);
console.log(el.tag);       // 'button'
console.log(el.classList); // ['btn', 'btn-primary']
getComputedStyles( path ) async

Shorthand — returns just the computed styles map for a node.

const css = await vre.getComputedStyles(buttons[0].path);
console.log(css['font-size']); // '13px'
getBoxModel( path ) · getLayout( path ) async

getBoxModel() returns the full CSS box model (margin, border, padding, content). getLayout() returns just the bounding box in iframe viewport coordinates.

const box  = await vre.getBoxModel(buttons[0].path);
// { margin, border, padding, content: { x, y, width, height } }
const rect = await vre.getLayout(buttons[0].path);
// { x: 32, y: 88, width: 94, height: 32 }
getAccessibilityTree() async

Returns a role/name accessibility tree derived from the DOM snapshot.

const a11y = await vre.getAccessibilityTree();
// { role: 'generic', tag: 'html', children: [...] }
highlightElement( pathOrSelector ) · clearHighlight() sync

Highlight a DOM node in the preview (same as hovering in the Elements panel). clearHighlight() removes any active highlight overlay.

vre.highlightElement(buttons[0].path); // by path array
vre.highlightElement('.hero');          // by CSS selector
vre.clearHighlight();
patchStyle( selector, property, value, opts? ) · applyCSS( css, opts? ) sync

patchStyle() live-patches a CSS property and writes back to the VFS source. applyCSS() injects arbitrary CSS without touching the VFS — pass { id } to overwrite on subsequent calls.

vre.patchStyle('.hero', 'background', 'red');
vre.patchStyle('.card', 'padding', '32px', { sourceFile: '/style.css' });
vre.applyCSS('.hero { display: none; }', { id: 'hide-hero' });
refreshDOM() · inspectStart() · inspectStop() · isInspecting() sync

refreshDOM() forces a fresh DOM snapshot and re-renders the Elements panel. inspectStart() activates the element picker. inspectStop() deactivates it. isInspecting() returns true if the picker is active.

vre.refreshDOM();
vre.inspectStart();
if (vre.isInspecting()) vre.inspectStop();
Section 10

Agent / AI interface

Designed for AI agents, automated testing, and IDE integrations.

getContext( opts? ) async

The single most important agent method. Returns a complete serialisable snapshot of everything — status, files, DOM, errors, logs, network — in one call. Pass this directly into an LLM prompt.

const ctx = await vre.getContext({
  includeDom:   true,  // default true
  includeFiles: true,  // default true
  domDepth:     4,     // DOM tree depth
  logLimit:     50,    // max log entries
});
// ctx: { status, files, errors, consoleLogs, networkLog, sources, dom, timestamp }
eval( code, opts? ) · evalAsync( code, opts? ) async

Evaluate a JavaScript expression inside the live preview iframe. evalAsync() wraps code in an async IIFE for await expressions. Rejects with EVAL_ERROR or TIMEOUT.

const title = await vre.eval('document.title');
const data  = await vre.evalAsync('return await fetch("/api").then(r => r.json())');
capture( opts? ) · captureElement( selector ) async

Take a PNG screenshot of the current preview viewport. Returns an ArrayBuffer of PNG bytes. captureElement() is a shorthand for capture({ selector }).

const png     = await vre.capture();
const cardPng = await vre.captureElement('.hero');
click( selectorOrX, y? ) async

Simulate a click by CSS selector or page coordinates.

await vre.click('#btn-run');
await vre.click(100, 200); // by coordinates
type( selector, text, opts? ) async

Type text into an input or textarea. Fires input and change events. Pass { clear: true } to clear existing value first.

await vre.type('#email', 'user@example.com', { clear: true });
scroll( opts? ) · hover( selector ) · focus( selector ) async

scroll() accepts { x, y } or { selector }. hover() fires mouseenter and mouseover. focus() focuses an element.

await vre.scroll({ selector: '.footer' });
await vre.scroll({ x: 0, y: 500 });
await vre.hover('.card');
await vre.focus('#search-input');
waitForElement( selector, opts? ) · waitFor( expression, opts? ) · waitForConsole( matcher ) · waitForRequest( pattern, opts? ) async

Poll the live preview until a condition is met. All reject with { code: 'TIMEOUT' } on expiry. waitForConsole() accepts a string or predicate function. waitForRequest() accepts a substring or regex.

await vre.waitForElement('.results', { timeout: 10000 });
await vre.waitFor('window.appReady === true');
await vre.waitForConsole('[app.js] Init complete');
const req = await vre.waitForRequest('/api/data', { method: 'POST' });
runScript( steps ) async

Run a declarative sequence of API actions. Never throws — collects errors and returns { results, errors }.

const { results, errors } = await vre.runScript([
  { action: 'eval',           args: ['document.title'] },
  { action: 'click',          args: ['#btn-run'] },
  { action: 'waitForConsole', args: ['Init complete'] },
  { action: 'capture',        args: [] },
]);
Section 11

Event system

Subscribe to engine events to react to the preview in real time.

on( event, fn ) · off( event, fn ) · once( event, fn ) · clearListeners( event? ) event

Standard subscribe/unsubscribe pattern. once() auto-unsubscribes after first firing. clearListeners() with no argument clears all events.

vre.on('consoleLog', e => console.log('[preview]', e.level, e.message));
vre.on('error', e => console.warn('Preview error:', e.message));
vre.on('networkLog', e => console.log(e.method, e.url, e.status));
vre.on('fileChange', e => console.log(`File ${e.type}:`, e.path));
vre.once('load', e => console.log('First load:', e.entryPath));
Event Payload Description
'ready' Service worker and engine ready
'load' { entryPath } Preview finished loading a page
'stop' Preview stopped
'reset' opts Hard reset performed
'navigate' { path } URL changed in the preview
'consoleLog' { level, message, origin, timestamp } Any console output
'error' { level, message, origin, timestamp } console.error or uncaught exception
'networkLog' { url, method, status, duration, destination, timestamp } Network request captured
'domSnapshot' { tree } Full DOM refresh
'inspectElement' { tag, id, classList, computedStyle, boundingRect, … } Element selected in inspector
'stylePatch' { selector, property, value } CSS patch applied
'fileChange' { type, path } File added, updated, deleted, or renamed
'sourcesUpdate' { sources } Sources panel updated
'snapshotRestored' { token } Filesystem snapshot restored
Section 12

UI / shell control

setTheme( theme ) sync

Switch the shell colour theme. Built-in values: 'dark', 'light', 'system'. Pass a token object for a custom theme.

vre.setTheme('light');
vre.setTheme('dark');
setToolbarVisible( visible ) sync

Show or hide the entire toolbar. Pass false for embed-only mode.

vre.setToolbarVisible(false); // hide for embed mode
vre.setToolbarVisible(true);
setDevtoolsHeight( height ) sync

Set the devtools panel height in pixels.

vre.setDevtoolsHeight(400);
setReadOnly( enabled ) sync

Lock the preview — hides play/stop/URL bar, disables inspector pick and style editing. Pass true for viewer mode, false to restore editing.

vre.setReadOnly(true);  // viewer mode
vre.setReadOnly(false); // restore editing
setTitle( title ) sync

Set placeholder text in the URL bar.

vre.setTitle('My Sandbox');
addToolbarButton( opts ) sync

Add a custom button to the toolbar. Returns a handle with a remove() method.

const btn = vre.addToolbarButton({
  icon: '<svg>...</svg>',
  title: 'Export',
  id: 'btn-export',
  onClick: () => console.log(vre.exportProject())
});
btn.remove();
showNotification( opts ) sync

Show a transient notification banner inside the shell. Types: 'info' (default) · 'success' · 'warn' · 'error'. Duration in ms, default 3000. Set to 0 for no auto-dismiss.

vre.showNotification({ message: 'Saved!', type: 'success' });
const n = vre.showNotification({ message: 'Working...', type: 'info', duration: 0 });
n.dismiss();
getIframe() · getRoot() sync

getIframe() returns the raw preview <iframe> element. getRoot() returns the shell root <div> element.

const iframe = vre.getIframe();
const root   = vre.getRoot();
setIframeInteractionBlocked( blocked, cursor? ) sync

Temporarily places an interaction shield over the preview iframe. Useful when a host IDE or outer shell has resize handles that may drag across the iframe. Common cursor values: 'ew-resize', 'ns-resize', 'grabbing'.

splitter.addEventListener('mousedown', () => {
  vre.setIframeInteractionBlocked(true, 'ew-resize');
});
document.addEventListener('mouseup', () => {
  vre.setIframeInteractionBlocked(false);
});
postMessage( msg ) sync

Send a raw postMessage to the preview iframe.

vre.postMessage({ type: 'myCustomEvent', data: { foo: 'bar' } });
blockPatterns( patterns ) · unblockPatterns( patterns ) · getBlockedPatterns() sync

Add or remove URL substring patterns to hide from console, network, and sources panels. Case-insensitive substring matches against the full URL.

vre.blockPatterns(['hotjar', 'segment', 'intercom']);
vre.unblockPatterns('hotjar');
console.log(vre.getBlockedPatterns());
Section 13

Batch helpers

writeFiles( files, opts? ) sync

Write multiple files in one shot — hot-reloads the preview once at the end instead of once per file.

vre.writeFiles([
  { path: '/index.html', content: newHtml },
  { path: '/style.css',  content: newCss },
  { path: '/app.js',     content: newJs },
], { entryPath: '/dashboard.html' });
exportProject( opts? ) · importProject( project, opts? ) sync

Export the entire filesystem as a serialisable JSON object — useful for saving and sharing. importProject() restores it, optionally running on import. Pass { includeContent: false } to export metadata only.

const project = vre.exportProject();
// { meta: { exportedAt, entryPath, fileCount }, files, filesystem }

localStorage.setItem('my-project', JSON.stringify(project));

const saved = JSON.parse(localStorage.getItem('my-project'));
vre.importProject(saved, { run: true });
Section 14

Complete setup example

A full working integration showing all major APIs wired together.

const vre = VRE.mount({
  ownerId: 'YOUR_OWNER_ID',
  container: "#vre",
  fileSystem,
  entryPath: "/index.html",
  devtoolsOpen: true,
  devtoolsHeight: 280,
  blockPatterns: ['google-analytics', 'hotjar'],
});

vre.run();

vre.on('error',      e => console.warn('Preview error:', e.message));
vre.on('consoleLog', e => console.log('[preview]', e.level, e.message));
vre.on('networkLog', e => console.log('[net]', e.method, e.url, e.status));
vre.on('fileChange',  e => console.log(`File ${e.type}:`, e.path));

vre.on('load', async function onFirstLoad() {
  vre.off('load', onFirstLoad);

  const ctx = await vre.getContext();    // full snapshot for LLM
  await vre.getDOM({ fresh: true });
  const buttons = vre.queryDOM('button');

  await vre.waitForElement('#btn-run', { timeout: 5000 });
  await vre.click('#btn-run');

  if (buttons[0]) {
    const el  = await vre.getElement(buttons[0].path);
    const box = await vre.getBoxModel(buttons[0].path);
    console.log('Button styles:', el.computedStyle);
    console.log('Button box:', box);
  }

  const token = vre.snapshotFilesystem();
  vre.writeFiles([{ path: '/style.css', content: newCss }]);
  setTimeout(() => vre.restoreSnapshot(token), 3000);

  setTimeout(() => vre.open('/about/index.html'), 5000);
});
Section 15

Runtime access control

VRE supports two access modes. Choose based on your security requirements.

Mode Setup Security
ownerId Simplest — just your owner ID Authorized-domain check only
accessCheckUrl Requires a backend endpoint Strongest — private API key stays server-side, runtime performs final verification with CodeVRE

Strict mode — runtime-token exchange flow:

// Frontend
const vre = VRE.mount({
  container: '#vre',
  fileSystem,
  entryPath: '/index.html',
  accessCheckUrl: '/api/codevre/check',
});

// Express backend
app.post('/api/codevre/check', async (req, res) => {
  const result = await fetch(
    'https://us-central1-codevre-vre.cloudfunctions.net/exchangeRuntimeToken',
    {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        'x-codevre-api-key': process.env.CODEVRE_API_KEY, // server-only
      },
      body: JSON.stringify({ origin: req.body.origin }),
    }
  );
  res.json(await result.json());
});

Firebase Functions example:

exports.codevreCheck = onRequest(
  { cors: true },
  async (req, res) => {
    if (req.method !== 'POST') {
      res.status(405).json({ ok: false, error: 'method-not-allowed' });
      return;
    }
    const result = await fetch(
      'https://us-central1-codevre-vre.cloudfunctions.net/exchangeRuntimeToken',
      {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json',
          'x-codevre-api-key': process.env.CODEVRE_API_KEY,
        },
        body: JSON.stringify({ origin: req.body?.origin }),
      }
    );
    res.json(await result.json());
  }
);
API keys are server-only. Never expose private CodeVRE API keys in frontend code, browser bundles, or public repositories. Only short-lived runtime tokens should ever reach the browser.
src mode bypasses protection. Using src instead of fileSystem + entryPath skips the VRE runtime boot and access checks. Do not use it for secured deployments.
Strict mode is fail-closed. Network failures or invalid verification responses block the runtime automatically. The runtime never silently falls back to unrestricted execution.

Authorized domains and API-key requirements:

requireApiKey Mode allowed Notes
false ownerId Easiest setup — authorized-domain check only
true accessCheckUrl Requires backend endpoint and private API key

Cross-Origin-Opener-Policy (COOP) requirements:

Do not set Cross-Origin-Opener-Policy: same-origin on your host page. This header severs window.opener when VRE opens the external preview window, causing it to show a standalone warning instead of receiving your filesystem. Use unsafe-none (the browser default) or omit the header entirely on your app's HTML pages.
COEP on your host page also affects external preview. Setting Cross-Origin-Embedder-Policy: credentialless or require-corp on your host page can also sever the opener relationship. If you need COEP for SharedArrayBuffer, apply it only to specific routes — not the catch-all /**.

Firebase Hosting — safe headers config:

// firebase.json — safe for external preview mode
{
  "headers": [
    {
      "source": "**",
      "headers": [
        { "key": "Access-Control-Allow-Origin", "value": "*" },
        { "key": "Cross-Origin-Resource-Policy", "value": "cross-origin" }
        // ✗ Do NOT add Cross-Origin-Opener-Policy: same-origin here
        // ✗ Do NOT add Cross-Origin-Embedder-Policy here
      ]
    }
  ]
}

Local dev server — safe headers:

// Node.js dev server — safe for external preview mode
res.setHeader('Access-Control-Allow-Origin', '*');
res.setHeader('Cross-Origin-Resource-Policy', 'cross-origin');
// ✗ Do NOT set Cross-Origin-Opener-Policy: same-origin
// ✗ Do NOT set Cross-Origin-Embedder-Policy on your app page
Section 16

Error codes

All async methods that can fail reject with an Error with a stable .code string.

Code Method(s) Description
TIMEOUT eval, waitFor, waitForElement, waitForConsole, waitForRequest, getDOM, getElement, waitForLoad Operation did not complete within the timeout
EVAL_ERROR eval, evalAsync The evaluated code threw a runtime error
CAPTURE_ERROR capture, captureElement Screenshot failed — no canvas or SVG found
FILE_EXISTS addFile File already exists and overwrite was not set
NOT_FOUND deleteFile, renameFile, diffFile, restoreSnapshot File or snapshot not found
try {
  await vre.waitForElement('.results', { timeout: 3000 });
} catch (e) {
  if (e.code === 'TIMEOUT') console.warn('Results never appeared');
  else throw e;
}

try {
  await vre.eval('badFunction()');
} catch (e) {
  if (e.code === 'EVAL_ERROR') console.warn('Eval failed:', e.message);
}