Anvil Cloud / Getting started
Examples
The anvil-cloud repository includes two examples that serve different jobs:
| Example | Use it for |
|---|---|
examples/notes |
Canonical local-first demo: React/Vite UI, auth, database, query, mutation, endpoint, job, workflow, generated client, local Lens, and CLI JSON. |
examples/aws-preview |
AWS-compatible smoke Cell for preview deploy, inspect, logs, auth rejection, and destroy. It stays small so preview smoke failures point at the adapter, not a sprawling demo. |
Start with examples/notes when learning the Cell model. Use
examples/aws-preview when validating AWS preview behavior.
The Cloud repository also includes agent contract examples under
docs/examples/agents: a local Project Agent, a local Agent Cell, an AWS
Bedrock Agent Cell, and provider registration. These are contract examples, not
separate runnable workspaces.
anvil cloud new --template <template> scaffolds runnable starter Cells shaped like
the same examples:
| Template | Pattern it demonstrates |
|---|---|
crud |
table, query, mutation, generated client |
auth |
auth-required query/mutation with owner-scoped rows |
workflow |
durable workflow alongside the starter CRUD path |
service |
supervised service alongside the starter CRUD path |
agent |
mounted agent with an approval contract and local invocation |
sandbox |
sandbox-required mounted agent |
anvil cloud new support-cell --template agent --client headless
All templates keep the starter listTodos/addTodo generated client path
working so they can be checked, built, and extended by agents without first
repairing the scaffold.
Notes example
The notes Cell defines:
- a
notestable withtitle,body,archived, andownerId - a public
statusquery - an authenticated
listNotesquery - authenticated
createNoteandarchiveNotemutations - a public
/api/healthendpoint - a
summarizeNotejob - an
onboardUserworkflow - a React/Vite client that uses generated query/mutation metadata
import {
app,
boolean,
endpoint,
job,
mutation,
query,
table,
text,
userId,
workflow,
} from "@anvil-cloud/runtime";
export default app({
schema: {
notes: table({
title: text().min(1).max(120),
body: text().max(2000).optional(),
archived: boolean().default(false),
ownerId: userId(),
}),
},
capabilities: {
database: true,
jobs: true,
workflows: true,
},
queries: {
status: query({
auth: "public",
handler: async () => ({ ok: true, cell: "notes" }),
}),
listNotes: query({
auth: "required",
handler: async (ctx) => {
return ctx.db.notes.where("ownerId", "=", ctx.auth.requireUser()).all();
},
}),
},
mutations: {
createNote: mutation<{ title: string; body?: string }>({
auth: "required",
handler: async (ctx, input) => {
const note = await ctx.db.notes.insert({
title: input.title,
body: input.body ?? "",
archived: false,
ownerId: ctx.auth.requireUser(),
});
await ctx.jobs.enqueue("summarizeNote", { noteId: note.id });
return note;
},
}),
},
endpoints: {
health: endpoint({
method: "GET",
path: "/api/health",
auth: "none",
handler: async () => ({ ok: true, cell: "notes" }),
}),
},
jobs: {
summarizeNote: job({
handler: async (ctx, payload) => {
await ctx.log.info("Summarize note job received", payload);
return { summarized: true };
},
}),
},
workflows: {
onboardUser: workflow({
steps: [
{
name: "seedWelcomeNote",
handler: async (ctx) => {
return ctx.db.notes.insert({
title: "Welcome to Anvil Notes",
body: "This note was created by a local workflow.",
archived: false,
ownerId: ctx.auth.requireUser(),
});
},
},
],
}),
},
});
The full source lives in examples/notes/src/cell.server.ts. The client source
is in examples/notes/src/client.
Common patterns
Mounting a Cell Agent
import { app, defineAgent, endpoint } from "@anvil-cloud/runtime";
const support = defineAgent({
name: "support",
instructions: "./agents/support/instructions.md",
model: { provider: "local", model: "stub" },
capabilities: {
cells: ["read"],
filesystem: "none",
secrets: "none",
},
approvals: {
requiredFor: ["email.sendExternal"],
},
});
export default app({
agents: { support },
endpoints: {
chat: endpoint({
method: "POST",
path: "/api/chat",
auth: "required",
agent: "support",
handler: async () => ({ ok: true }),
}),
},
});
Validate and invoke it locally:
anvil cloud agents validate --json
anvil cloud agents manifest --json
anvil cloud agents invoke support --input "Review this Cell" --json
Filtering with where clauses
const recent = await ctx.db.notes
.where("createdAt", ">", Date.now() - 86400000)
.all();
Supported operators: =, !=, >, >=, <, <=.
Requiring auth
const userId = ctx.auth.requireUser();
Throws AUTH_REQUIRED if no user is authenticated.
Checking roles
if (!ctx.auth.hasRole("admin")) {
throw new Error("FORBIDDEN");
}
Using environment variables
const apiKey = ctx.env.require("OPENAI_API_KEY");
Never use process.env directly. Guard rejects it.
Logging
ctx.log.info("Note created", { noteId: note.id });
Logs are written to .anvil/local/logs.ndjson locally and CloudWatch in AWS preview.
File uploads
capabilities: {
files: {
publicRead: false,
}
}
// in a mutation or endpoint
await ctx.files.put("uploads/avatar.png", buffer);
const file = await ctx.files.get("uploads/avatar.png");
Outbound fetch
capabilities: {
outboundFetch: {
allow: ["api.stripe.com"],
}
}
// Guard checks that fetch calls match declared hosts
const res = await fetch("https://api.stripe.com/v1/customers");
Running the example
cd anvil-cloud/examples/notes
node ../../packages/cli/dist/index.js check --json
node ../../packages/cli/dist/index.js dev --port 8787 --client-port 5173
Create a local user and token:
node ../../packages/cli/dist/index.js auth add-user local_demo \
--email demo@example.test \
--roles admin \
--json
TOKEN=$(node ../../packages/cli/dist/index.js auth token local_demo --json | jq -r .token)
Call the runtime directly:
curl -X POST http://localhost:8787/_anvil/mutation/createNote \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"input":{"title":"Hello","body":"World"}}'
curl -X POST http://localhost:8787/_anvil/query/listNotes \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"input":{}}'
Or run the repeatable smoke verifier:
cd anvil-cloud
pnpm verify:notes-local
Inspecting the example
While anvil cloud dev is running:
node ../../packages/cli/dist/index.js lens --json
node ../../packages/cli/dist/index.js inspect --local --json
node ../../packages/cli/dist/index.js logs --local --json
node ../../packages/cli/dist/index.js db dump notes --local --json