Complete client-only shared counter
Create podda.json using the manifest topic, then add index.html and app.js. Import the SDK exactly as documented; do not probe or feature-detect method names.
<!doctype html><html lang="en"><meta name="viewport" content="width=device-width"><title>Shared counter</title><body><main><h1>Shared counter</h1><output id="count">0</output><button id="add">+1</button><p id="status"></p></main><script type="module" src="./app.js"></script></body></html>
import { init, PoddaSdkError } from "/__podda/v2/sdk.js";
const app = await init();
const count = document.querySelector("#count");
const status = document.querySelector("#status");
async function read() {
try { return await app.kv.get("counter"); }
catch (error) {
if (error instanceof PoddaSdkError && error.code === "KV_NOT_FOUND") return { value: 0, version: undefined };
throw error;
}
}
async function render() { count.textContent = String((await read()).value); }
document.querySelector("#add").addEventListener("click", async () => {
for (let attempt = 0; attempt < 6; attempt += 1) {
const current = await read();
try {
const next = await app.kv.set("counter", Number(current.value) + 1, { ifVersion: current.version, idempotencyKey: crypto.randomUUID() });
count.textContent = String(next.value); status.textContent = "Saved"; return;
} catch (error) {
if (!(error instanceof PoddaSdkError) || error.code !== "VERSION_CONFLICT") throw error;
}
}
status.textContent = "Busy—please try again.";
});
render().catch((error) => { status.textContent = error.message; app.errors.capture(error); });
For a leaderboard, store one CAS-protected JSON object such as { total, members: { [app.user.id]: { name, count } } }, obtain display names from app.members.list(), and recompute the entire object after every conflict.
For a generated-server variant, declare the server capability and server.mjs entrypoint, then use this complete asset-fallback module:
export default {
async fetch(request, env) {
const url = new URL(request.url);
if (url.pathname !== "/api/health")
return url.pathname.startsWith("/api/")
? Response.json({ error: { code: "ROUTE_NOT_FOUND" } }, { status: 404 })
: env.ASSETS.fetch(request);
if (request.method !== "GET")
return new Response("Method not allowed", { status: 405, headers: { Allow: "GET" } });
const session = await fetch(new URL("/__podda/v2/session", request.url));
if (!session.ok) return Response.json({ ready: false }, { status: session.status });
const { user } = await session.json();
return Response.json({ ready: true, user_id: user.id });
}
};
Last updated 22 August 2026 · Documentation version 7