Quickstarts
Full Stack App
Build a Userland app with static UI, server routes, auth, data, and secrets.
For agents: Model durable needs in
resources, keep secret reads in server code, and usectxhelpers instead of direct platform APIs.
File Structure
manifest.userland.json
public/index.html
public/assets/app.js
public/assets/app.css
server/index.js
Resource Shape
Declare auth, data, and secrets before writing runtime code that depends on them. If activation returns pending_secrets, set the missing secrets and republish or inspect the release state.
Manifest
{
"app": {
"name": "Team Notes",
"summary": "A private notes app with admin-managed access.",
"visibility": "public"
},
"runtime": {
"static_root": "public",
"server_entry": "server/index.js",
"fallback": "server"
},
"resources": {
"auth": {
"mode": "app_users",
"roles": ["admin", "editor"],
"public_signup": false
},
"data": {
"collections": {
"notes": {
"fields": {
"title": "string",
"body": "richtext",
"status": { "type": "enum", "values": ["draft", "published"] }
},
"indexes": [{ "name": "by_status", "fields": ["status"] }],
"access": { "read": "authenticated", "write": "role:editor" }
}
}
},
"secrets": { "required": ["OPENAI_API_KEY"] }
}
}
Server entry
export default {
async fetch(request, ctx) {
const url = new URL(request.url);
const notes = ctx.data.collection("notes");
if (url.pathname === "/api/notes" && request.method === "GET") {
await ctx.auth.requireRole(request, "editor");
return Response.json(await notes.list({ limit: 50 }));
}
if (url.pathname === "/api/notes" && request.method === "POST") {
await ctx.auth.requireRole(request, "editor");
const input = await request.json();
const note = await notes.create({
title: input.title,
body: input.body,
status: "draft"
});
return Response.json({ note }, { status: 201 });
}
return new Response("not found", { status: 404 });
}
};
Publish and operate
npm run userland -- apps publish . --message "Initial Team Notes release"
printf '%s' "$OPENAI_API_KEY" | npm run userland -- apps secrets set "$APP_ID" OPENAI_API_KEY
npm run userland -- apps events "$APP_ID" --severity error --limit 25
Verification
- Static files are under
runtime.static_root. - Server routes return JSON errors without stack traces.
- Browser code never contains secret names with values.
- Publish output includes
activation_status=live.