Install and prepare a page
Use Node.js 22.23 or later on a supported platform. This example needs no global CLI, MCP registration, or Docker. Create an example directory:
mkdir previewhost-library
cd previewhost-library
npm init -y
npm install previewhost
mkdir site
printf '<h1>Hello from Previewhost.</h1>\n' > site/index.htmlStart a preview
Save this as preview.mjs beside the site directory:
import { resolve } from 'node:path';
import { createPreviewRuntime } from 'previewhost';
const runtime = await createPreviewRuntime({
allowedRoots: [process.cwd()],
});
try {
const started = await runtime.start({
name: 'site',
type: 'static',
directory: resolve('site'),
});
const ready = await runtime.wait('site', started.candidate.id);
if (ready.state !== 'ready') throw new Error(JSON.stringify(ready.error));
const response = await fetch(ready.url);
if (!response.ok) throw new Error(`HTTP ${response.status}`);
console.log(await response.text());
} finally {
await runtime.close();
}Run the program from that directory:
node preview.mjsThe program prints <h1>Hello from Previewhost.</h1> and stops the preview.
start returns before startup finishes. wait follows the returned attempt ID until startup succeeds or fails.
If the request fails, the finally block still closes the runtime.
For a longer-lived preview, keep the runtime open until your application finishes with it.
Run your application
The runtime accepts the same spec types as the CLI and MCP: static, command, attach, and environment.
Direct specs require absolute source paths within allowedRoots.
To reuse a file, import loadPreviewSpec from previewhost and pass its result to runtime.start:
const spec = await loadPreviewSpec('./preview.yaml');File paths resolve relative to the configuration file. See Write preview.yaml for commands, readiness checks, and service connections.
Native commands and managed databases require an authorize callback in createPreviewRuntime.
The callback must decide whether to allow the requested operation. Without it, these operations fail with EXECUTION_DENIED.
Managed databases also require dataDirectory, a private directory outside the source tree, and the database prerequisites.
See runtime configuration for callback arguments and selected inputs.
For stored secrets, select the exact reference names in secretIds when you create the runtime.
Stored secrets and managed databases also require an unlocked runtime.keystore session.
runtime.keystore.status() reports new, locked, or unlocked and attempts remembered automatic unlock on macOS.
For an existing locked keystore, collect the password through your host's private input.
Call runtime.keystore.unlock({ password }) before startup.
Use keystore setup to create storage before this workflow. Unlocking the CLI or dashboard does not unlock this runtime.
runtime.close() also closes its keystore session.
Embed in a desktop host
The host creates one runtime per profile. stateDirectory, dataDirectory, and keystoreDirectory must use private directories outside application sources.
stateDirectory retains definitions and native ownership records. Startup reconciles those records without executing application commands again.
Recovery signals a process group only after its recorded process identity matches. Unresolved ownership remains visible as cleanup debt.
The host must keep source folders until cleanup succeeds.
Packaged Electron hosts supply supervisor: { executable, module, env }.
The executable uses Electron's Node mode, and the module points to the packaged supervisor.js outside ASAR.
The host supplies ELECTRON_RUN_AS_NODE: "1" in that supervisor environment.
Application environment variables still pass through Previewhost's restricted environment construction.
To run the repository's commands as another user, supply applicationProcesses.
The host then starts and stops their supervisor and recovers them after a crash.
resolvePreviewFile(project) selects preview.yaml or preview.yml and rejects ambiguous defaults.
configureBindings edits the retained definition without exposing concealed values.
describe and log reads expose redacted results. The host never needs a plaintext secret-read endpoint.
For exact-source Design verification, prepareCandidate holds a candidate before public promotion.
candidateUrl returns its verification endpoint. promote accepts the exact attempt and an optional settlement callback.
A rejected callback restores the old route, or clears it if no application was serving.
A successful callback commits promotion before retiring the old process.
The host owns source capture, verification, and its transaction. Previewhost owns serving and process cleanup.
The runtime retains up to eight attempts in memory. Logs expire after eviction or owner restart. A normal shutdown joins resource cleanup. A client window closing does not independently stop an environment.
Connect to an existing daemon
Use connectPreviewDaemon to control a daemon that already runs. It implements the same PreviewApi interface as the runtime.
import { connectPreviewDaemon } from 'previewhost';
const client = connectPreviewDaemon({
endpoint: 'http://127.0.0.1:9400',
tokenFile: '/absolute/private-directory/token',
});
try {
console.log(await client.list());
} finally {
await client.close();
}Replace the endpoint and token path with those of your daemon.
client.close() ends client requests and leaves previews active. client.shutdown() stops the daemon and its previews.