getting-started
Getting started
Create an account, get a project ID, install the SDK, and run your first browser Node or Python process.
Create an account
Verklet requires a project ID before Runtime.boot() can load runtime
assets or registry configuration. Sign up at /account, then
select or create an organization. Verklet provisions a default project
for that organization and shows a public ID that starts with prj_.
Copy that ID into your SDK boot options. It is safe to ship in browser
code, but it must come from your Verklet account; demo IDs such as
prj_demo are placeholders.
Install
npm install @verklet/sdk
# or
pnpm add @verklet/sdk
# or
yarn add @verklet/sdk
The SDK is published as ESM-only. It expects a browser environment with
SharedArrayBuffer available — see
browser support for the cross-origin isolation
headers your page needs to serve.
Boot a runtime
import { Runtime } from '@verklet/sdk';
const runtime = await Runtime.boot({
projectId: 'prj_your_project_id',
backend: 'auto',
persistenceKey: 'my-first-runtime',
});
Runtime.boot() spins up the coordinator worker, sets up the virtual
filesystem, and resolves once everything is ready. projectId is a
public identifier from your Verklet account. The boot service uses it to
load compatible hosted runtime assets, choose the package registry,
enable server-runtime access when available, and report usage. Unknown
project IDs are rejected. With a persistenceKey, browser filesystem
state is hydrated from OPFS if the visitor has been here before.
backend: 'auto' starts in the browser, runs supported Python through
Pyodide, and promotes to the server only for commands that need it, such
as uv, unsupported pip packages, or native tools. Use
backend: 'browser' for browser-only sessions or backend: 'server'
for server-first sessions.
Preview domain allowlist
Hosted Verklet previews load from
https://<session>.preview.verklet.com/preview/<port>/. The hosted boot
endpoint returns that preview origin template automatically, so SDK users
normally do not need to configure a preview domain.
If your app sets a Content Security Policy, allow Verklet previews in the places you render or call them:
frame-src https://*.preview.verklet.com;
child-src https://*.preview.verklet.com;
connect-src https://verklet.com https://registry.verklet.com https://broker.verklet.com wss://broker.verklet.com;
Use the frame-src directive for preview iframes. Keep child-src if
you support older CSP implementations. Use runtime.fetchPreview() when
your host page needs to inspect preview responses directly. Corporate
firewall and egress allowlists should also allow
*.preview.verklet.com.
Server promotion needs a short-lived broker grant from your backend. The
SDK asks for it through server.grant instead of calling a grant
endpoint for you, so your application can attach its own auth headers
and policy checks. The grant callback receives the project ID, runtime
ID, workspace key, browser installation ID, and requested action:
const runtime = await Runtime.boot({
projectId: 'prj_your_project_id',
backend: 'auto',
server: {
grant: async (request) => {
const response = await fetch('/api/verklet/server-grant', {
method: 'POST',
headers: {
authorization: `Bearer ${await getYourAppSessionToken()}`,
'content-type': 'application/json',
},
body: JSON.stringify(request),
});
if (!response.ok) {
throw new Error(`Server grant denied: ${response.status}`);
}
return response.json();
},
},
});
Keep vks_... private grant secrets on your backend. The browser should
only receive short-lived grants scoped to the project, runtime,
workspace, and action it is about to perform. In your backend endpoint,
verify the current user can use the requested projectId and
workspaceKey, then forward the request to Verklet's hosted grant
endpoint with your private secret:
await fetch(
`https://verklet.com/api/runtime/server/grants?projectId=${encodeURIComponent(body.projectId)}`,
{
method: 'POST',
headers: {
authorization: `Bearer ${process.env.VERKLET_PRIVATE_GRANT_SECRET}`,
'content-type': 'application/json',
},
body: JSON.stringify(body),
},
);
See server runtime for a complete endpoint example, public minting caveats, and server persistence deletion.
Mount some files
await runtime.mount({
'/workdir': {
'package.json': { contents: '{"type":"module"}' },
'index.mjs': { contents: 'console.log("hello from a tab")' },
'hello.py': { contents: 'print("hello from Pyodide")' },
},
});
The mount API takes a tree object. Files contain contents; directories
contain nested entries. You can mount as many trees as you like; later
mounts overlay earlier ones.
Spawn a process
const proc = await runtime.spawn('node', ['index.mjs'], {
cwd: '/workdir',
});
for await (const chunk of proc.output) {
console.log(chunk);
}
await proc.exit;
spawn returns a process handle with an async-iterable output stream,
an exit promise that resolves with the exit code, and stdin/kill
methods for interactive control.
Supported Python uses the same API and runs in Pyodide in the browser:
const py = await runtime.spawn('python', ['hello.py'], {
cwd: '/workdir',
});
for await (const chunk of py.output) {
console.log(chunk);
}
await py.exit;
For packages that Pyodide ships, configure browser preloading at boot:
const runtime = await Runtime.boot({
projectId: 'prj_your_project_id',
backend: 'auto',
python: { preloadPackages: ['matplotlib'] },
});
Listen for server-ready events
If your spawned process opens a port, Verklet emits a server-ready
event you can use to fetch from it or open it in an iframe:
runtime.on('server-ready', async ({ port, url }) => {
console.log(`server is up on :${port}`);
// `url` is a service-worker-routed URL on this origin.
iframeRef.current.src = url;
});
Tear down
await runtime.teardown();
Call this on unmount to release worker handles and free resources. If
persistOnTeardown was set, the filesystem is flushed through the
active backend first: OPFS for browser sessions, server persistence for
server sessions.