Workflow for editing space ""
The loop
- Read.
state.glob("**/*"),state.readFile,state.searchText. - Edit.
state.writeFile,state.replaceInFile. Multi-file edits:state.planEdits+state.applyEdits. - Build (validate).
host.build()(insiderun_code) bundles the working tree as a dry-run and returns{ snapshotHash, fromCache, assetCount, warnings }. Fix any warnings. Optional —host.publish()always builds first, so a standalonebuild()is only for checking. - Publish (go live).
host.publish()builds + swaps the single live facet, returning{ ok, status, live, snapshotHash, instanceId, url?, visibility? }—url+visibility: "public"appear onceliveis true; a failure carrieserrorinstead. - Showcase.
place.add("/the-page", "Label")— register the page as a place so the owner sees it as a live frame in the dashboard the moment it ships (a/__-prefixed place stays owner-only; the edge 404s/__*to the public). See run-code.
The working tree is the source, and one space has one live facet. The
snapshotHash is the content identity of a build (a pure function of source
files + asset config).
Elaborate example: stateful counter
This builds a tiny stateful Worker that persists a counter across requests
using the App DO's KV storage (see dynamic-worker
for capabilities). The whole flow happens through one run_code call.
async () => {
// Write the worker source. Named `App` export — required by the bundler
// (see bundling: unfolder://docs/bundling).
await state.writeFile(
"/src/index.ts",
`import { DurableObject } from "cloudflare:workers";
export class App extends DurableObject {
async fetch(request) {
const url = new URL(request.url);
if (url.pathname === "/reset") {
this.ctx.storage.kv.put("counter", 0);
return new Response("reset");
}
let counter = this.ctx.storage.kv.get("counter") ?? 0;
counter++;
this.ctx.storage.kv.put("counter", counter);
return new Response("count=" + counter + " path=" + url.pathname);
}
}
`,
);
// Go live in the same call.
return await host.publish(); // → { ok: true, status: "complete", live: true, snapshotHash, instanceId, url, visibility: "public" }
};
To validate without going live, call host.build() first:
async () => host.build() // → { snapshotHash, fromCache, assetCount, warnings }
async () => host.publish() // → { ok, status, live, snapshotHash, instanceId, url: ".../", visibility } — url once live
A request to https://<your-space-slug>.unfolder.space/hello returns count=1 path=/hello,
and the second request returns count=2. The live facet preserves the App DO's
SQLite across publishes, so the counter survives a republish.
Reading and exploring
state.* (exposed inside run_code) is the full file API. Drop into run_code and
call the workspace primitives directly. To preserve template literals
(e.g. ${name}) verbatim in a string you're writing, use write_files
instead — those bytes are not JS-evaluated.
// Walk the workspace (nested tree, max depth 3)
async () => state.walkTree("/", { maxDepth: 3 })
// Find files by pattern
async () => state.glob("src/**/*.{ts,tsx}")
// One-level listing
async () => state.readdirWithFileTypes("/src")
// Read a text file
async () => state.readFile("/src/index.ts")
// Read a binary file as base64 (MCP transport is JSON; pick an encoding)
async () => {
const bytes = await state.readFileBytes("/photo.png");
return { encoding: "base64", contents: btoa(String.fromCharCode(...bytes)) };
}
// Search content across files (searchText is single-file — use searchFiles for a glob)
async () => state.searchFiles("**/*", "TODO")
// Aggregate counters for a subtree → { files, directories, symlinks, totalBytes, maxDepth }.
// (The `unfolder://spaces/<your-space-slug>` resource carries a separate whole-workspace
// aggregate as `workspaceInfo` — { fileCount, directoryCount, totalBytes, r2FileCount }.)
async () => state.summarizeTree("/")
Iterating safely
Edits live in the working tree until you host.publish(), so iterate freely
with state.* and host.build() (a dry-run, no go-live) until the build is
clean, then publish once. The live facet's SQLite is preserved across publishes,
so persisted state survives a republish.