Platform
Hosting & deploysPush to a branch, get a live linkDatabaseReal Postgres, one per appSign-inEmail and Google, users includedFile storageUploads, photos and documentsPaymentsCheckout and plans, same projectAI gatewayEvery model, with a hard spend cap
Solutions
Moving an app overFrom Lovable, Bolt, Replit or v0Your first real appNever shipped anything beforeSide project → businessWhen people start paying youPricing$20/month plus prepaid credits
Resources
DocsGuides written for people who are newFor AI agentsClaude Code, Cursor, MCP, JSON CLIChangelogWhat shipped, most recent firstComponentsPaste-ready UI for sign-in and moreSupportA person answers, not a queue botRefer a friendBoth of you get credits Start for $20
Open in ChatGPT Open in Claude Last updated 2026-09-24

Plax framework

The app framework Camplax ships — routes, owner-scoped data, sign-in, in-app agents, and a manifest agents can read.

Plax is the framework every camplax create app is built on — and the framework your coding agent writes in when it works on your repo. It is deliberately small: a Node HTTP server, owner-scoped resources, Better Auth sign-in, server-only AI, and in-app agents. What makes it different is not the feature list but the shape: there is one way to do each thing, the unsafe way fails closed, and the app can describe itself.

Routes and auth

createPlaxApp takes a route table. Keys look like GET /api/notes or GET /api/notes/:id:param segments capture into ctx.params. A route is either a plain handler (public) or { auth: true, handler }, which resolves the Better Auth session before the handler runs and guarantees ctx.user.

const app = createPlaxApp({
  session: sessionLookup,
  staticDir: "public",
  routes: {
    "GET /probe": (_req, res) => sendOk(res, { ok: true }),
    "GET /api/me": {
      auth: true,
      handler: (_req, res, ctx) => sendOk(res, { user: ctx.user }),
    },
  },
});

An auth: true route without a session lookup throws at boot, not at request time. Errors thrown in handlers return a generic 500 — internal details are logged, never sent to the client.

Resources — data with an owner

defineResource is the only way a Plax app touches a table. Every read, update and delete carries ownerField = session.user.id inside the query itself; the client cannot set id or the owner; only fields are writable; required and validate gate creates. A row you cannot see answers 404, not 403.

resource.routes() mounts the whole REST surface — GET/POST on /api/<name>, GET/PATCH/DELETE on /api/<name>/:id — already auth-guarded, with ?limit/?offset pagination capped by the store. postgresStore parameterizes every value and validates every configured identifier, so hand-built identifiers cannot inject SQL.

const notes = defineResource<Note>({
  name: "notes",
  fields: ["title"],
  required: ["title"],
  store: postgresStore<Note>({ client: db, table: "notes", columns: { /* … */ } }),
});

routes: { ...notes.routes() }

camplax add resource <name> writes the whole slice — the migration, the resource, the mount instructions — in one step.

Agents inside your app

agent() defines an in-app agent: a name, a system prompt, tools, a thread store, and a budget. agent.routes() mounts POST /api/<name>/chat, auth-guarded like everything else.

const support = agent({
  name: "support",
  ai,
  system: "You help users with their orders.",
  tools: {
    lookupOrder: tool({
      args: { id: "string" },
      run: ({ id }, { user }) => orders.get(user, id),
    }),
  },
  threads: postgresThreads(db),
  budget: { capUsdPerDay: 0.10, store: postgresBudget(db) },
});

The loop is the standard one — model, tool calls, validated arguments, results, repeat — capped by maxSteps. Three properties come from the platform, not the framework:

  • Tools cannot cross users. lookupOrder calls orders.get(user, id) — the resource layer scopes it, so user A's agent cannot return user B's rows.
  • Spend is capped per end-user. The budget table is checked before every model call; 429 budget_exceeded when spent, and your project's credit wallet still applies beneath it.
  • Dangerous tools pause. approval: { refundOrder: "user" } returns pendingApprovals; the client POSTs approve/deny to resume. Threads persist, so the loop survives the round trip.

camplax add agent <name> writes the agent file and the threads/usage migration.

Streaming. POST "stream": true to /api/<name>/chat and the reply is server-sent events, token by token:

EventCarries
threadthe thread id, first
reasoning_deltaa chunk of the model's thinking, as it arrives
message_deltaa chunk of the reply text, as it arrives
tool_call / tool_resulta tool starting / finishing, with args and result
approval_requiredthe loop paused on an approval-gated tool
messagethe assembled reply text
error / donea typed failure / the end of the stream

Reasoning models stream their thinking through reasoning_delta, and the reasoning is persisted on the thread and replayed on later turns so the model keeps its context. Without "stream", the route answers one JSON reply — same loop, same envelope. Either way, closing the connection aborts the in-flight model call, so a disconnected client cannot keep burning credits.

Any provider. The ai in agent({ ai }) is createPlaxAi — the Camplax gateway by default, but chatUrl + token point the same agent at any OpenAI-compatible endpoint (OpenAI, OpenRouter, Groq, Ollama, a LiteLLM proxy). The key stays server-side either way.

The manifest

app.manifest() emits plax.manifest.json — routes with auth flags, resources with fields and tables, agents with tools and budgets, required and injected env vars, cron, plugins. It is the app's contract with everything around it:

  • Coding agents read it instead of guessing the surface.
  • camplax check validates the app against it — declared env present, resources wired, typecheck green — in one --json report.
  • The platform can diff what an app says it needs against what the project provides.

The starter writes it at boot via writeManifest(app.manifest(), root).

Why it looks like this

Plax is built for the agent era in both directions. For the agents writing your app: one way per thing, a manifest to read, and camplax check as the feedback loop. For the agents inside your app: tools that cannot leak across owners, spend that stops at zero, and threads that are just rows in your own database. And it stays Camplax-shaped throughout — Better Auth on your own Postgres, AI through the gateway on prepaid credits, observability through the same-origin proxy.

© 2026 Camplax $20 / month · credits stop at zero
Ask about Camplax